1
0
Fork 0

Initial Commit

master
Liza 2023-09-30 13:19:46 +02:00
commit 161bf7e39c
Signed by: liza
GPG Key ID: 7199F8D727D55F67
16 changed files with 1078 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/.idea
*.user

16
ARControl.sln Normal file
View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ARControl", "ARControl\ARControl.csproj", "{B33BF820-56C2-45A1-AEEC-3DCF526DBF42}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B33BF820-56C2-45A1-AEEC-3DCF526DBF42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B33BF820-56C2-45A1-AEEC-3DCF526DBF42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B33BF820-56C2-45A1-AEEC-3DCF526DBF42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B33BF820-56C2-45A1-AEEC-3DCF526DBF42}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

3
ARControl/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/dist
/obj
/bin

View File

@ -0,0 +1,83 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<Version>1.0</Version>
<LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<OutputPath>dist</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>portable</DebugType>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
<AutoRetainerLibPath>$(appdata)\XIVLauncher\installedPlugins\AutoRetainer\4.1.2.5\</AutoRetainerLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dalamud.ContextMenu" Version="1.2.3"/>
<PackageReference Include="DalamudPackager" Version="2.1.11"/>
</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="ImGuiScene">
<HintPath>$(DalamudLibPath)ImGuiScene.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>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="AutoRetainerAPI">
<HintPath>$(AutoRetainerLibPath)AutoRetainerAPI.dll</HintPath>
</Reference>
<Reference Include="ECommons">
<HintPath>$(AutoRetainerLibPath)ECommons.dll</HintPath>
</Reference>
<Reference Include="ClickLib">
<HintPath>$(AutoRetainerLibPath)ClickLib.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="External\" />
</ItemGroup>
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin">
<Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip"/>
</Target>
</Project>

7
ARControl/ARControl.json Normal file
View File

@ -0,0 +1,7 @@
{
"Name": "ARC",
"Author": "Liza Carvelli",
"Punchline": "Better AutoRetainer Venture Distribution",
"Description": "",
"RepoUrl": "https://git.carvel.li/liza/ARControl"
}

View File

@ -0,0 +1,303 @@
using System;
using System.Linq;
using ARControl.GameData;
using ARControl.Windows;
using AutoRetainerAPI;
using Dalamud.Data;
using Dalamud.Game.ClientState;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Plugin;
using ECommons;
using ImGuiNET;
namespace ARControl;
public sealed class AutoRetainerControlPlugin : IDalamudPlugin
{
private readonly WindowSystem _windowSystem = new(nameof(AutoRetainerControlPlugin));
private readonly DalamudPluginInterface _pluginInterface;
private readonly DataManager _dataManager;
private readonly ClientState _clientState;
private readonly ChatGui _chatGui;
private readonly CommandManager _commandManager;
private readonly Configuration _configuration;
private readonly GameCache _gameCache;
private readonly ConfigWindow _configWindow;
private readonly AutoRetainerApi _autoRetainerApi;
public AutoRetainerControlPlugin(DalamudPluginInterface pluginInterface, DataManager dataManager,
ClientState clientState, ChatGui chatGui, CommandManager commandManager)
{
_pluginInterface = pluginInterface;
_dataManager = dataManager;
_clientState = clientState;
_chatGui = chatGui;
_commandManager = commandManager;
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
_gameCache = new GameCache(_dataManager);
_configWindow = new ConfigWindow(_pluginInterface, _configuration, _gameCache, _clientState, _commandManager);
_windowSystem.AddWindow(_configWindow);
ECommonsMain.Init(_pluginInterface, this);
_autoRetainerApi = new();
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle;
_autoRetainerApi.OnSendRetainerToVenture += SendRetainerToVenture;
_autoRetainerApi.OnRetainerPostVentureTaskDraw += RetainerTaskButtonDraw;
_clientState.TerritoryChanged += TerritoryChanged;
_commandManager.AddHandler("/arc", new CommandInfo(ProcessCommand));
if (_autoRetainerApi.Ready)
Sync();
}
public string Name => "ARC";
private void SendRetainerToVenture(string retainerName)
{
var ch = _configuration.Characters.SingleOrDefault(x => x.LocalContentId == _clientState.LocalContentId);
if (ch == null)
{
PluginLog.Information("No character information found");
}
else if (!ch.Managed)
{
PluginLog.Information("Character is not managed");
}
else
{
var retainer = ch.Retainers.SingleOrDefault(x => x.Name == retainerName);
if (retainer == null)
{
PluginLog.Information("No retainer information found");
}
else if (!retainer.Managed)
{
PluginLog.Information("Retainer is not managed");
}
else
{
PluginLog.Information("Checking tasks...");
Sync();
foreach (var queuedItem in _configuration.QueuedItems.Where(x => x.RemainingQuantity > 0))
{
PluginLog.Information($"Checking venture info for itemId {queuedItem.ItemId}");
var venture = _gameCache.Ventures
.Where(x => retainer.Level >= x.Level)
.FirstOrDefault(x => x.ItemId == queuedItem.ItemId && x.MatchesJob(retainer.Job));
if (venture == null)
{
PluginLog.Information($"No applicable venture found for itemId {queuedItem.ItemId}");
}
else
{
var itemToGather = _gameCache.ItemsToGather.FirstOrDefault(x => x.ItemId == queuedItem.ItemId);
if (itemToGather != null && !ch.GatheredItems.Contains(itemToGather.GatheredItemId))
{
PluginLog.Information($"Character hasn't gathered {venture.Name} yet");
}
else
{
PluginLog.Information(
$"Found venture {venture.Name}, row = {venture.RowId}, checking if it is suitable");
VentureReward? reward = null;
if (venture.CategoryName is "MIN" or "BTN")
{
if (retainer.Gathering >= venture.RequiredGathering)
reward = venture.Rewards.Last(
x => retainer.Perception >= x.PerceptionMinerBotanist);
}
else if (venture.CategoryName == "FSH")
{
if (retainer.Gathering >= venture.RequiredGathering)
reward = venture.Rewards.Last(
x => retainer.Perception >= x.PerceptionFisher);
}
else
{
if (retainer.ItemLevel >= venture.ItemLevelCombat)
reward = venture.Rewards.Last(
x => retainer.ItemLevel >= x.ItemLevelCombat);
}
if (reward == null)
{
PluginLog.Information(
"Retainer doesn't have enough stats for the venture and would return no items");
}
else
{
_chatGui.Print(
$"ARC → Overriding venture to collect {reward.Quantity}x {venture.Name}.");
PluginLog.Information(
$"Setting AR to use venture {venture.RowId}, which should retrieve {reward.Quantity}x {venture.Name}");
_autoRetainerApi.SetVenture(venture.RowId);
queuedItem.RemainingQuantity =
Math.Max(0, queuedItem.RemainingQuantity - reward.Quantity);
_pluginInterface.SavePluginConfig(_configuration);
return;
}
}
}
}
// fallback: managed but no venture found
if (retainer.LastVenture != 395)
{
_chatGui.Print("ARC → No tasks left, using QC");
PluginLog.Information($"No tasks left (previous venture = {retainer.LastVenture}), using QC");
_autoRetainerApi.SetVenture(395);
}
else
PluginLog.Information("Not changing venture plan, already 395");
}
}
}
private void RetainerTaskButtonDraw(ulong characterId, string retainerName)
{
Configuration.CharacterConfiguration? characterConfiguration = _configuration.Characters.FirstOrDefault(x => x.LocalContentId == characterId);
if (characterConfiguration is not { Managed: true })
return;
Configuration.RetainerConfiguration? retainer = characterConfiguration.Retainers.FirstOrDefault(x => x.Name == retainerName);
if (retainer is not { Managed: true })
return;
ImGui.SameLine();
ImGuiComponents.IconButton(FontAwesomeIcon.Book);
}
private void TerritoryChanged(object? sender, ushort e) => Sync();
public void Sync()
{
bool save = false;
// FIXME This should have a way to get blacklisted character ids
foreach (ulong registeredCharacterId in _autoRetainerApi.GetRegisteredCharacters())
{
PluginLog.Information($"ch → {registeredCharacterId:X}");
var offlineCharacterData = _autoRetainerApi.GetOfflineCharacterData(registeredCharacterId);
if (offlineCharacterData.ExcludeRetainer)
continue;
var character = _configuration.Characters.SingleOrDefault(x => x.LocalContentId == registeredCharacterId);
if (character == null)
{
character = new Configuration.CharacterConfiguration
{
LocalContentId = registeredCharacterId,
CharacterName = offlineCharacterData.Name,
WorldName = offlineCharacterData.World,
Managed = false,
};
save = true;
_configuration.Characters.Add(character);
}
if (character.GatheredItems != offlineCharacterData.UnlockedGatheringItems)
{
character.GatheredItems = offlineCharacterData.UnlockedGatheringItems;
save = true;
}
foreach (var retainerData in offlineCharacterData.RetainerData)
{
var retainer = character.Retainers.SingleOrDefault(x => x.Name == retainerData.Name);
if (retainer == null)
{
retainer = new Configuration.RetainerConfiguration
{
Name = retainerData.Name,
Managed = false,
};
save = true;
character.Retainers.Add(retainer);
}
if (retainer.DisplayOrder != retainerData.DisplayOrder)
{
retainer.DisplayOrder = retainerData.DisplayOrder;
save = true;
}
if (retainer.Level != retainerData.Level)
{
retainer.Level = retainerData.Level;
save = true;
}
if (retainer.Job != retainerData.Job)
{
retainer.Job = retainerData.Job;
save = true;
}
if (retainer.LastVenture != retainerData.VentureID)
{
retainer.LastVenture = retainerData.VentureID;
save = true;
}
var additionalData =
_autoRetainerApi.GetAdditionalRetainerData(registeredCharacterId, retainerData.Name);
if (retainer.ItemLevel != additionalData.Ilvl)
{
retainer.ItemLevel = additionalData.Ilvl;
save = true;
}
if (retainer.Gathering != additionalData.Gathering)
{
retainer.Gathering = additionalData.Gathering;
save = true;
}
if (retainer.Perception != additionalData.Perception)
{
retainer.Perception = additionalData.Perception;
save = true;
}
}
}
if (save)
_pluginInterface.SavePluginConfig(_configuration);
}
private void ProcessCommand(string command, string arguments)
{
if (arguments == "sync")
Sync();
else
_configWindow.Toggle();
}
public void Dispose()
{
_commandManager.RemoveHandler("/arc");
_clientState.TerritoryChanged -= TerritoryChanged;
_autoRetainerApi.OnRetainerPostVentureTaskDraw -= RetainerTaskButtonDraw;
_autoRetainerApi.OnSendRetainerToVenture -= SendRetainerToVenture;
_pluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_autoRetainerApi.Dispose();
ECommonsMain.Dispose();
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Dalamud.Configuration;
namespace ARControl;
internal sealed class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 1;
public List<QueuedItem> QueuedItems { get; set; } = new();
public List<CharacterConfiguration> Characters { get; set; } = new();
public sealed class QueuedItem
{
public required uint ItemId { get; set; }
public required int RemainingQuantity { get; set; }
}
public sealed class CharacterConfiguration
{
public required ulong LocalContentId { get; set; }
public required string CharacterName { get; set; }
public required string WorldName { get; set; }
public required bool Managed { get; set; }
public List<RetainerConfiguration> Retainers { get; set; } = new();
public HashSet<uint> GatheredItems { get; set; } = new();
public override string ToString() => $"{CharacterName} @ {WorldName}";
}
public sealed class RetainerConfiguration
{
public required string Name { get; set; }
public required bool Managed { get; set; }
public int DisplayOrder { get; set; }
public int Level { get; set; }
public uint Job { get; set; }
public uint LastVenture { get; set; }
public int ItemLevel { get; set; }
public int Gathering { get; set; }
public int Perception { get; set; }
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
<DalamudPackager
ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
MakeZip="false"
VersionComponents="2"/>
</Target>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<DalamudPackager
ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
MakeZip="true"
VersionComponents="2"
Exclude="ARDiscard.deps.json;AutoRetainerAPI.pdb;ClickLib.pdb;ClickLib.xml;ECommons.pdb;ECommons.xml"/>
</Target>
</Project>

View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
namespace ARControl.GameData;
internal sealed class GameCache
{
public GameCache(DataManager dataManager)
{
Jobs = dataManager.GetExcelSheet<ClassJob>()!.ToDictionary(x => x.RowId, x => x.Abbreviation.ToString());
Ventures = dataManager.GetExcelSheet<RetainerTask>()!
.Where(x => x.RowId > 0 && !x.IsRandom && x.Task != 0)
.Select(x => new Venture(dataManager, x))
.ToList()
.AsReadOnly();
ItemsToGather = dataManager.GetExcelSheet<GatheringItem>()!
.Where(x => x.RowId > 0 && x.RowId < 10_000 && x.Item != 0 && x.Quest.Row == 0)
.Where(x => Ventures.Any(y => y.ItemId == x.Item))
.Select(x => new ItemToGather(dataManager, x))
.OrderBy(x => x.Name)
.ToList()
.AsReadOnly();
}
public IReadOnlyDictionary<uint, string> Jobs { get; }
public IReadOnlyList<Venture> Ventures { get; }
public IReadOnlyList<ItemToGather> ItemsToGather { get; }
}

View File

@ -0,0 +1,19 @@
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
namespace ARControl.GameData;
internal sealed class ItemToGather
{
public ItemToGather(DataManager dataManager, GatheringItem item)
{
GatheredItemId = item.RowId;
ItemId = item.Item;
Name = dataManager.GetExcelSheet<Item>()!.GetRow((uint)item.Item)!.Name.ToString();
}
public uint GatheredItemId { get; }
public int ItemId { get; }
public string Name { get; }
}

View File

@ -0,0 +1,93 @@
using System.Collections.Generic;
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
namespace ARControl.GameData;
internal sealed class Venture
{
public Venture(DataManager dataManager, RetainerTask retainerTask)
{
RowId = retainerTask.RowId;
Category = retainerTask.ClassJobCategory.Value!;
var taskDetails = dataManager.GetExcelSheet<RetainerTaskNormal>()!.GetRow(retainerTask.Task)!;
var taskParameters = retainerTask.RetainerTaskParameter.Value!;
ItemId = taskDetails.Item.Row;
Name = taskDetails.Item.Value!.Name.ToString();
Level = retainerTask.RetainerLevel;
ItemLevelCombat = retainerTask.RequiredItemLevel;
RequiredGathering = retainerTask.RequiredGathering;
Rewards = new List<VentureReward>
{
new VentureReward
{
Quantity = taskDetails.Quantity[0],
ItemLevelCombat = 0,
PerceptionMinerBotanist = 0,
PerceptionFisher = 0,
},
new VentureReward
{
Quantity = taskDetails.Quantity[1],
ItemLevelCombat = taskParameters.ItemLevelDoW[0],
PerceptionMinerBotanist = taskParameters.PerceptionDoL[0],
PerceptionFisher = taskParameters.PerceptionFSH[0],
},
new VentureReward
{
Quantity = taskDetails.Quantity[2],
ItemLevelCombat = taskParameters.ItemLevelDoW[1],
PerceptionMinerBotanist = taskParameters.PerceptionDoL[1],
PerceptionFisher = taskParameters.PerceptionFSH[1],
},
new VentureReward
{
Quantity = taskDetails.Quantity[3],
ItemLevelCombat = taskParameters.ItemLevelDoW[2],
PerceptionMinerBotanist = taskParameters.PerceptionDoL[2],
PerceptionFisher = taskParameters.PerceptionFSH[2],
},
new VentureReward
{
Quantity = taskDetails.Quantity[4],
ItemLevelCombat = taskParameters.ItemLevelDoW[3],
PerceptionMinerBotanist = taskParameters.PerceptionDoL[3],
PerceptionFisher = taskParameters.PerceptionFSH[3],
}
};
}
public uint RowId { get; }
public ClassJobCategory Category { get; }
public string? CategoryName
{
get
{
return Category.RowId switch
{
17 => "MIN",
18 => "BTN",
19 => "FSH",
_ => "DoWM",
};
}
}
public uint ItemId { get; }
public string Name { get; }
public byte Level { get; }
public ushort ItemLevelCombat { get; }
public ushort RequiredGathering { get; set; }
public List<VentureReward> Rewards { get; }
public bool MatchesJob(uint job)
{
if (Category.RowId >= 17 && Category.RowId <= 19)
return Category.RowId == job + 1;
else
return job is < 16 or > 18;
}
}

View File

@ -0,0 +1,9 @@
namespace ARControl.GameData;
internal sealed class VentureReward
{
public required byte Quantity { get; init; }
public required short ItemLevelCombat { get; init; }
public required short PerceptionMinerBotanist { get; init; }
public required short PerceptionFisher { get; init; }
}

View File

@ -0,0 +1,408 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using ARControl.GameData;
using Dalamud.Game.ClientState;
using Dalamud.Game.Command;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Plugin;
using ECommons.ImGuiMethods;
using ImGuiNET;
namespace ARControl.Windows;
internal sealed class ConfigWindow : Window
{
private const byte MaxLevel = 90;
private static readonly Vector4 ColorGreen = ImGuiColors.HealerGreen;
private static readonly Vector4 ColorRed = ImGuiColors.DalamudRed;
private static readonly Vector4 ColorGrey = ImGuiColors.DalamudGrey;
private readonly DalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;
private readonly GameCache _gameCache;
private readonly ClientState _clientState;
private readonly CommandManager _commandManager;
private string _searchString = string.Empty;
private Configuration.QueuedItem? _dragDropSource;
private bool _enableDragDrop;
private bool _checkPerCharacter = true;
private bool _onlyShowMissing = true;
public ConfigWindow(
DalamudPluginInterface pluginInterface,
Configuration configuration,
GameCache gameCache,
ClientState clientState,
CommandManager commandManager)
: base("ARC###ARControlConfig")
{
_pluginInterface = pluginInterface;
_configuration = configuration;
_gameCache = gameCache;
_clientState = clientState;
_commandManager = commandManager;
}
public override void Draw()
{
if (ImGui.BeginTabBar("ARConfigTabs"))
{
DrawItemQueue();
DrawCharacters();
DrawGatheredItemsToCheck();
ImGui.EndTabBar();
}
}
private unsafe void DrawItemQueue()
{
if (ImGui.BeginTabItem("Venture Queue"))
{
if (ImGui.BeginCombo("Venture...##VentureSelection", ""))
{
ImGuiEx.SetNextItemFullWidth();
ImGui.InputTextWithHint("", "Filter...", ref _searchString, 256);
foreach (var ventures in _gameCache.Ventures
.Where(x => x.Name.ToLower().Contains(_searchString.ToLower()))
.OrderBy(x => x.Level)
.ThenBy(x => x.Name)
.ThenBy(x => x.ItemId)
.GroupBy(x => x.ItemId))
{
var venture = ventures.First();
if (ImGui.Selectable(
$"{venture.Name} ({string.Join(" ", ventures.Select(x => x.CategoryName))})##SelectVenture{venture.RowId}"))
{
_configuration.QueuedItems.Add(new Configuration.QueuedItem
{
ItemId = venture.ItemId,
RemainingQuantity = 0,
});
_searchString = string.Empty;
Save();
}
}
ImGui.EndCombo();
}
ImGui.Checkbox("Enable Drag&Drop", ref _enableDragDrop);
ImGui.Separator();
ImGui.Indent(30);
Configuration.QueuedItem? itemToRemove = null;
Configuration.QueuedItem? itemToAdd = null;
int indexToAdd = 0;
for (int i = 0; i < _configuration.QueuedItems.Count; ++i)
{
var item = _configuration.QueuedItems[i];
ImGui.PushID($"QueueItem{i}");
var ventures = _gameCache.Ventures.Where(x => x.ItemId == item.ItemId).ToList();
var venture = ventures.First();
if (!_enableDragDrop)
{
ImGui.SetNextItemWidth(130);
int quantity = item.RemainingQuantity;
if (ImGui.InputInt($"{venture.Name} ({string.Join(" ", ventures.Select(x => x.CategoryName))})",
ref quantity, 100))
{
item.RemainingQuantity = quantity;
Save();
}
}
else
{
ImGui.Selectable($"{item.RemainingQuantity}x {venture.Name}");
if (ImGui.BeginDragDropSource())
{
ImGui.SetDragDropPayload("ArcDragDrop", nint.Zero, 0);
_dragDropSource = item;
ImGui.EndDragDropSource();
}
if (ImGui.BeginDragDropTarget())
{
if (_dragDropSource != null && ImGui.AcceptDragDropPayload("ArcDragDrop").NativePtr != null)
{
itemToAdd = _dragDropSource;
indexToAdd = i;
_dragDropSource = null;
}
ImGui.EndDragDropTarget();
}
}
ImGui.OpenPopupOnItemClick($"###ctx{i}", ImGuiPopupFlags.MouseButtonRight);
if (ImGui.BeginPopup($"###ctx{i}"))
{
if (ImGui.Selectable($"Remove {venture.Name}"))
itemToRemove = item;
ImGui.EndPopup();
}
ImGui.PopID();
}
if (itemToRemove != null)
{
_configuration.QueuedItems.Remove(itemToRemove);
Save();
}
if (itemToAdd != null)
{
PluginLog.Information($"Updating {itemToAdd.ItemId} → {indexToAdd}");
_configuration.QueuedItems.Remove(itemToAdd);
_configuration.QueuedItems.Insert(indexToAdd, itemToAdd);
Save();
}
ImGui.Unindent(30);
ImGui.EndTabItem();
}
}
private void DrawCharacters()
{
if (ImGui.BeginTabItem("Retainers"))
{
foreach (var world in _configuration.Characters
.Where(x => x.Retainers.Any(y => y.Job != 0))
.OrderBy(x => x.LocalContentId)
.GroupBy(x => x.WorldName))
{
ImGui.CollapsingHeader(world.Key, ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.Bullet);
foreach (var character in world)
{
ImGui.PushID($"Char{character.LocalContentId}");
ImGui.PushItemWidth(ImGui.GetFontSize() * 30);
Vector4 buttonColor = new Vector4();
if (character.Managed && character.Retainers.Count > 0)
{
if (character.Retainers.All(x => x.Managed))
buttonColor = ImGuiColors.HealerGreen;
else if (character.Retainers.All(x => !x.Managed))
buttonColor = ImGuiColors.DalamudRed;
else
buttonColor = ImGuiColors.DalamudOrange;
}
if (ImGuiComponents.IconButton(FontAwesomeIcon.Book, buttonColor))
{
character.Managed = !character.Managed;
Save();
}
ImGui.SameLine();
if (ImGui.CollapsingHeader(
$"{character.CharacterName} {(character.Managed ? $"({character.Retainers.Count(x => x.Managed)} / {character.Retainers.Count})" : "")}###{character.LocalContentId}"))
{
ImGui.Indent(30);
foreach (var retainer in character.Retainers.Where(x => x.Job > 0).OrderBy(x => x.DisplayOrder))
{
ImGui.BeginDisabled(retainer.Level < MaxLevel);
bool managed = retainer.Managed && retainer.Level == MaxLevel;
ImGui.Text(_gameCache.Jobs[retainer.Job]);
ImGui.SameLine();
if (ImGui.Checkbox($"{retainer.Name}###Retainer{retainer.Name}{retainer.DisplayOrder}",
ref managed))
{
retainer.Managed = managed;
Save();
}
ImGui.EndDisabled();
}
ImGui.Unindent(30);
}
ImGui.PopID();
}
}
ImGui.EndTabItem();
}
}
private void DrawGatheredItemsToCheck()
{
if (ImGui.BeginTabItem("Locked Items"))
{
ImGui.Checkbox("Group by character", ref _checkPerCharacter);
ImGui.Checkbox("Only show missing items", ref _onlyShowMissing);
ImGui.Separator();
var itemsToCheck =
_configuration.QueuedItems
.Select(x => x.ItemId)
.Distinct()
.Select(itemId => new
{
GatheredItem = _gameCache.ItemsToGather.SingleOrDefault(x => x.ItemId == itemId),
Ventures = _gameCache.Ventures.Where(x => x.ItemId == itemId).ToList()
})
.Where(x => x.GatheredItem != null && x.Ventures.Count > 0)
.Select(x => new CheckedItem
{
GatheredItem = x.GatheredItem!,
Ventures = x.Ventures,
ItemId = x.Ventures.First().ItemId,
})
.ToList();
var charactersToCheck = _configuration.Characters
.Where(x => x.Managed)
.OrderBy(x => x.WorldName)
.ThenBy(x => x.LocalContentId)
.Select(x => new CheckedCharacter(x, itemsToCheck))
.ToList();
if (_checkPerCharacter)
{
foreach (var ch in charactersToCheck.Where(x => x.ToCheck(_onlyShowMissing).Any()))
{
bool currentCharacter = _clientState.LocalContentId == ch.Character.LocalContentId;
ImGui.BeginDisabled(currentCharacter);
if (ImGuiComponents.IconButton($"SwitchChacters{ch.Character.LocalContentId}",
FontAwesomeIcon.DoorOpen))
{
_commandManager.ProcessCommand($"/ays relog {ch.Character.CharacterName}@{ch.Character.WorldName}");
}
ImGui.EndDisabled();
ImGui.SameLine();
if (currentCharacter)
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
bool expanded = ImGui.CollapsingHeader($"{ch.Character}###GatheredCh{ch.Character.LocalContentId}");
if (currentCharacter)
ImGui.PopStyleColor();
if (expanded)
{
ImGui.Indent(30);
foreach (var item in itemsToCheck.Where(x =>
ch.ToCheck(_onlyShowMissing).ContainsKey(x.ItemId)))
{
var color = ch.Items[item.ItemId];
if (color != ColorGrey)
{
ImGui.PushStyleColor(ImGuiCol.Text, color);
if (currentCharacter && color == ColorRed)
{
ImGui.Selectable(item.GatheredItem.Name);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
uint classJob = _clientState.LocalPlayer!.ClassJob.Id;
if (classJob == 16)
_commandManager.ProcessCommand($"/gathermin {item.GatheredItem.Name}");
else if (classJob == 17)
_commandManager.ProcessCommand($"/gatherbtn {item.GatheredItem.Name}");
else if (classJob == 18)
_commandManager.ProcessCommand($"/gatherfish {item.GatheredItem.Name}");
else
_commandManager.ProcessCommand($"/gather {item.GatheredItem.Name}");
}
}
else
{
ImGui.Text(item.GatheredItem.Name);
}
ImGui.PopStyleColor();
}
}
ImGui.Unindent(30);
}
}
}
else
{
foreach (var item in itemsToCheck.Where(x =>
charactersToCheck.Any(y => y.ToCheck(_onlyShowMissing).ContainsKey(x.ItemId))))
{
if (ImGui.CollapsingHeader($"{item.GatheredItem.Name}##Gathered{item.GatheredItem.ItemId}"))
{
ImGui.Indent(30);
foreach (var ch in charactersToCheck)
{
var color = ch.Items[item.ItemId];
if (color == ColorRed || (color == ColorGreen && !_onlyShowMissing))
ImGui.TextColored(color, ch.Character.ToString());
}
ImGui.Unindent(30);
}
}
}
ImGui.EndTabItem();
}
}
private void Save()
{
_pluginInterface.SavePluginConfig(_configuration);
}
private sealed class CheckedCharacter
{
public CheckedCharacter(Configuration.CharacterConfiguration character,
List<CheckedItem> itemsToCheck)
{
Character = character;
foreach (var item in itemsToCheck)
{
bool enabled = character.Retainers.Any(x => item.Ventures.Any(v => v.MatchesJob(x.Job)));
if (enabled)
{
if (character.GatheredItems.Contains(item.GatheredItem.GatheredItemId))
Items[item.ItemId] = ColorGreen;
else
Items[item.ItemId] = ColorRed;
}
else
Items[item.ItemId] = ColorGrey;
}
}
public Configuration.CharacterConfiguration Character { get; }
public Dictionary<uint, Vector4> Items { get; } = new();
public Dictionary<uint, Vector4> ToCheck(bool onlyShowMissing)
{
return Items
.Where(x => x.Value == ColorRed || (x.Value == ColorGreen && !onlyShowMissing))
.ToDictionary(x => x.Key, x => x.Value);
}
}
private sealed class CheckedItem
{
public required ItemToGather GatheredItem { get; init; }
public required List<Venture> Ventures { get; init; }
public required uint ItemId { get; init; }
}
}

View File

@ -0,0 +1,19 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"Dalamud.ContextMenu": {
"type": "Direct",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "ydemplF7DNcA/LLeongDVzWUD/JV0Fw3EwA2+P0jYq3Le2ZYSt4U8qyJq4FyoChqt0lFG8BxYCAzfeWp4Jmnqw=="
},
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.11, )",
"resolved": "2.1.11",
"contentHash": "9qlAWoRRTiL/geAvuwR/g6Bcbrd/bJJgVnB/RurBiyKs6srsP0bvpoo8IK+Eg8EA6jWeM6/YJWs66w4FIAzqPw=="
}
}
}
}

13
README.md Normal file
View File

@ -0,0 +1,13 @@
# ARC
Instead of manually managing retainer plans for each individual character,
which can get fairly tedious fairly quickly, we optimize this a bit and
only manage "high-level" plans.
This means we create a list a la:
- 5000 Cobalt Ore
- 2000 Gold Ore
... and ARC distributes each venture automatically amongst all characters
which are included.

7
global.json Normal file
View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}