This commit is contained in:
Liza 2024-03-20 19:52:54 +01:00
parent 7d5cbd8563
commit 1ac1d89809
Signed by: liza
GPG Key ID: 7199F8D727D55F67
19 changed files with 1085 additions and 49 deletions

1017
.editorconfig Normal file

File diff suppressed because it is too large Load Diff

2
LLib

@ -1 +1 @@
Subproject commit 865a6080319f8ccbcd5fd5b0004404822b6e60d4
Subproject commit 3792244261a9f5426a7916f5a6dd1966238ba84a

View File

@ -0,0 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Amalj_0027aa/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ceruleum/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Workshoppa/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Yesno/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Z_0027ranmaia/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -10,7 +10,7 @@ internal sealed class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 1;
public CurrentItem? CurrentlyCraftedItem { get; set; } = null;
public CurrentItem? CurrentlyCraftedItem { get; set; }
public List<QueuedItem> ItemQueue { get; set; } = new();
public bool EnableRepairKitCalculator { get; set; } = true;
public bool EnableCeruleumTankCalculator { get; set; } = true;
@ -27,7 +27,7 @@ internal sealed class Configuration : IPluginConfiguration
public uint WorkshopItemId { get; set; }
public bool StartedCrafting { get; set; }
public uint PhasesComplete { get; set; } = 0;
public uint PhasesComplete { get; set; }
public List<PhaseItem> ContributedItemsInCurrentPhase { get; set; } = new();
public bool UpdateFromCraftState(CraftState craftState)

View File

@ -8,7 +8,7 @@ public sealed class CraftState
public required uint ResultItem { get; init; }
public required uint StepsComplete { get; init; }
public required uint StepsTotal { get; init; }
public required List<CraftItem> Items { get; init; }
public required IReadOnlyList<CraftItem> Items { get; init; }
public bool IsPhaseComplete() => Items.All(x => x.Finished || x.StepsComplete == x.StepsTotal);

View File

@ -1,4 +1,5 @@
using System;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using Dalamud.Plugin.Services;
using LLib;
@ -13,19 +14,19 @@ internal sealed class GameStrings
public GameStrings(IDataManager dataManager, IPluginLog pluginLog)
{
PurchaseItemForGil = dataManager.GetRegex<Addon>(3406, addon => addon.Text, pluginLog)
?? throw new Exception($"Unable to resolve {nameof(PurchaseItemForGil)}");
?? throw new ConstraintException($"Unable to resolve {nameof(PurchaseItemForGil)}");
PurchaseItemForCompanyCredits = dataManager.GetRegex<Addon>(3473, addon => addon.Text, pluginLog)
?? throw new Exception($"Unable to resolve {nameof(PurchaseItemForCompanyCredits)}");
?? throw new ConstraintException($"Unable to resolve {nameof(PurchaseItemForCompanyCredits)}");
ViewCraftingLog =
dataManager.GetString<WorkshopDialogue>("TEXT_CMNDEFCOMPANYMANUFACTORY_00150_MENU_CC_NOTE",
pluginLog) ?? throw new Exception($"Unable to resolve {nameof(ViewCraftingLog)}");
pluginLog) ?? throw new ConstraintException($"Unable to resolve {nameof(ViewCraftingLog)}");
TurnInHighQualityItem = dataManager.GetString<Addon>(102434, addon => addon.Text, pluginLog)
?? throw new Exception($"Unable to resolve {nameof(TurnInHighQualityItem)}");
?? throw new ConstraintException($"Unable to resolve {nameof(TurnInHighQualityItem)}");
ContributeItems = dataManager.GetRegex<Addon>(6652, addon => addon.Text, pluginLog)
?? throw new Exception($"Unable to resolve {nameof(ContributeItems)}");
?? throw new ConstraintException($"Unable to resolve {nameof(ContributeItems)}");
RetrieveFinishedItem =
dataManager.GetRegex<WorkshopDialogue>("TEXT_CMNDEFCOMPANYMANUFACTORY_00150_FINISH_CONF", pluginLog)
?? throw new Exception($"Unable to resolve {nameof(RetrieveFinishedItem)}");
?? throw new ConstraintException($"Unable to resolve {nameof(RetrieveFinishedItem)}");
}
public Regex PurchaseItemForGil { get; }
@ -36,7 +37,8 @@ internal sealed class GameStrings
public Regex RetrieveFinishedItem { get; }
[Sheet("custom/001/CmnDefCompanyManufactory_00150")]
private class WorkshopDialogue : QuestDialogueText
[SuppressMessage("Performance", "CA1812")]
private sealed class WorkshopDialogue : QuestDialogueText
{
}
}

View File

@ -6,7 +6,7 @@ using Lumina.Excel.GeneratedSheets;
namespace Workshoppa.GameData;
public sealed class RecipeTree
internal sealed class RecipeTree
{
private readonly IDataManager _dataManager;
private readonly IReadOnlyList<uint> _shopItemsOnly;
@ -38,7 +38,7 @@ public sealed class RecipeTree
.AsReadOnly();
}
public List<Ingredient> ResolveRecipes(List<Ingredient> materials)
public IReadOnlyList<Ingredient> ResolveRecipes(IReadOnlyList<Ingredient> materials)
{
// look up recipes recursively
int limit = 10;
@ -72,7 +72,8 @@ public sealed class RecipeTree
//_pluginLog.Information($" → {part.Name}");
int unmodifiedQuantity = part.TotalQuantity;
int roundedQuantity = (int)((unmodifiedQuantity + ingredient.AmountCrafted - 1) / ingredient.AmountCrafted);
int roundedQuantity =
(int)((unmodifiedQuantity + ingredient.AmountCrafted - 1) / ingredient.AmountCrafted);
part.TotalQuantity = part.TotalQuantity - unmodifiedQuantity + roundedQuantity;
}
}
@ -83,13 +84,13 @@ public sealed class RecipeTree
List<RecipeInfo> sortedList = new List<RecipeInfo>();
while (sortedList.Count < completeList.Count)
{
var craftable = completeList.Where(x =>
!sortedList.Contains(x) && x.DependsOn.All(y => sortedList.Any(z => y == z.ItemId)))
var canBeCrafted = completeList.Where(x =>
!sortedList.Contains(x) && x.DependsOn.All(y => sortedList.Any(z => y == z.ItemId)))
.ToList();
if (craftable.Count == 0)
throw new Exception("Unable to sort items");
if (canBeCrafted.Count == 0)
throw new InvalidOperationException("Unable to sort items");
sortedList.AddRange(craftable.OrderBy(x => x.Name));
sortedList.AddRange(canBeCrafted.OrderBy(x => x.Name));
}
return sortedList.Cast<Ingredient>().ToList();
@ -142,7 +143,7 @@ public sealed class RecipeTree
return ingredients;
}
private List<RecipeInfo> ExtendWithAmountCrafted(List<Ingredient> materials)
private List<RecipeInfo> ExtendWithAmountCrafted(IEnumerable<Ingredient> materials)
{
return materials.Select(x => new
{
@ -164,17 +165,17 @@ public sealed class RecipeTree
.ToList();
}
public Recipe? GetFirstRecipeForItem(uint itemId)
private Recipe? GetFirstRecipeForItem(uint itemId)
{
return _dataManager.GetExcelSheet<Recipe>()!.FirstOrDefault(x => x.RowId > 0 && x.ItemResult.Row == itemId);
}
public GatheringItem? GetGatheringItem(uint itemId)
private GatheringItem? GetGatheringItem(uint itemId)
{
return _dataManager.GetExcelSheet<GatheringItem>()!.FirstOrDefault(x => x.RowId > 0 && (uint)x.Item == itemId);
}
public RetainerTaskNormal? GetVentureItem(uint itemId)
private RetainerTaskNormal? GetVentureItem(uint itemId)
{
return _dataManager.GetExcelSheet<RetainerTaskNormal>()!
.FirstOrDefault(x => x.RowId > 0 && x.Item.Row == itemId);

View File

@ -129,7 +129,7 @@ internal sealed class CeruleumTankWindow : ShopWindow
}
}
private string FormatStackCount(int ceruleumTanks)
private static string FormatStackCount(int ceruleumTanks)
{
int fullStacks = ceruleumTanks / 999;
int partials = ceruleumTanks % 999;

View File

@ -1,12 +1,11 @@
using System.Numerics;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using ImGuiNET;
using LLib;
using LLib.ImGui;
namespace Workshoppa.Windows;
internal sealed class ConfigWindow : LImGui.LWindow
internal sealed class ConfigWindow : LWindow
{
private readonly DalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;

View File

@ -13,12 +13,13 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using ImGuiNET;
using LLib;
using LLib.ImGui;
using Workshoppa.GameData;
namespace Workshoppa.Windows;
// FIXME The close button doesn't work near the workshop, either hide it or make it work
internal sealed class MainWindow : LImGui.LWindow
internal sealed class MainWindow : LWindow
{
private static readonly Regex CountAndName = new(@"^(\d{1,5})x?\s+(.*)$", RegexOptions.Compiled);
@ -231,7 +232,7 @@ internal sealed class MainWindow : LImGui.LWindow
ImGui.InputTextWithHint("", "Filter...", ref _searchString, 256);
foreach (var craft in _workshopCache.Crafts
.Where(x => x.Name.ToLower().Contains(_searchString.ToLower()))
.Where(x => x.Name.Contains(_searchString, StringComparison.OrdinalIgnoreCase))
.OrderBy(x => x.WorkshopItemId))
{
IDalamudTextureWrap? icon = _iconCache.GetIcon(craft.IconId);
@ -319,7 +320,7 @@ internal sealed class MainWindow : LImGui.LWindow
ImGui.InputTextWithHint("", "Preset Name...", ref _newPresetName, 64);
ImGui.BeginDisabled(_configuration.Presets.Any(x =>
x.Name.Equals(_newPresetName, StringComparison.CurrentCultureIgnoreCase)));
x.Name.Equals(_newPresetName, StringComparison.OrdinalIgnoreCase)));
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Save, "Save"))
{
_configuration.Presets.Add(new Configuration.Preset
@ -400,7 +401,7 @@ internal sealed class MainWindow : LImGui.LWindow
continue;
var craft = _workshopCache.Crafts.FirstOrDefault(x =>
x.Name.Equals(match.Groups[2].Value, StringComparison.CurrentCultureIgnoreCase));
x.Name.Equals(match.Groups[2].Value, StringComparison.OrdinalIgnoreCase));
if (craft != null && int.TryParse(match.Groups[1].Value, out int quantity))
{
fromClipboardItems.Add(new Configuration.QueuedItem
@ -590,7 +591,7 @@ internal sealed class MainWindow : LImGui.LWindow
.ToList();
}
private void AddMaterial(Dictionary<uint, int> completedForCurrentCraft, uint itemId, int quantity)
private static void AddMaterial(Dictionary<uint, int> completedForCurrentCraft, uint itemId, int quantity)
{
if (completedForCurrentCraft.TryGetValue(itemId, out var existingQuantity))
completedForCurrentCraft[itemId] = quantity + existingQuantity;

View File

@ -6,14 +6,14 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ImGuiNET;
using LLib;
using LLib.GameUI;
using LLib.ImGui;
using Workshoppa.External;
using Workshoppa.GameData.Shops;
namespace Workshoppa.Windows;
internal abstract class ShopWindow : LImGui.LWindow, IDisposable
internal abstract class ShopWindow : LWindow, IDisposable
{
private readonly string _addonName;
private readonly WorkshopPlugin _plugin;

View File

@ -51,12 +51,12 @@ partial class WorkshopPlugin
private void SelectCraftBranch()
{
if (SelectSelectString("contrib", 0, s => s.StartsWith("Contribute materials.")))
if (SelectSelectString("contrib", 0, s => s.StartsWith("Contribute materials.", StringComparison.Ordinal)))
{
CurrentStage = Stage.ContributeMaterials;
_continueAt = DateTime.Now.AddSeconds(1);
}
else if (SelectSelectString("advance", 0, s => s.StartsWith("Advance to the next phase of production.")))
else if (SelectSelectString("advance", 0, s => s.StartsWith("Advance to the next phase of production.", StringComparison.Ordinal)))
{
_pluginLog.Information("Phase is complete");
@ -67,7 +67,7 @@ partial class WorkshopPlugin
CurrentStage = Stage.TargetFabricationStation;
_continueAt = DateTime.Now.AddSeconds(3);
}
else if (SelectSelectString("complete", 0, s => s.StartsWith("Complete the construction of")))
else if (SelectSelectString("complete", 0, s => s.StartsWith("Complete the construction of", StringComparison.Ordinal)))
{
_pluginLog.Information("Item is almost complete, confirming last cutscene");
CurrentStage = Stage.TargetFabricationStation;

View File

@ -119,7 +119,7 @@ partial class WorkshopPlugin
private void ConfirmCraft()
{
if (SelectSelectYesno(0, s => s.StartsWith("Craft ")))
if (SelectSelectYesno(0, s => s.StartsWith("Craft ", StringComparison.Ordinal)))
{
_configuration.CurrentlyCraftedItem!.StartedCrafting = true;
_pluginInterface.SavePluginConfig(_configuration);

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
@ -122,7 +123,9 @@ partial class WorkshopPlugin
LAddon.IsAddonReady(&addonSelectYesno->AtkUnitBase))
{
var text = MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString();
text = text.Replace("\n", "").Replace("\r", "");
text = text
.Replace("\n", "", StringComparison.Ordinal)
.Replace("\r", "", StringComparison.Ordinal);
if (predicate(text))
{
_pluginLog.Information($"Selecting choice {choice} for '{text}'");
@ -184,17 +187,22 @@ partial class WorkshopPlugin
return null;
}
private uint ParseAtkItemCountHq(AtkValue atkValue)
private static uint ParseAtkItemCountHq(AtkValue atkValue)
{
// NQ / HQ string
// I have no clue, but it doesn't seme like the available HQ item count is strored anywhere in the atkvalues??
string? s = atkValue.ReadAtkString();
if (s != null)
{
var parts = s.Replace("\ue03c", "").Split('/');
var parts = s.Replace("\ue03c", "", StringComparison.Ordinal).Split('/');
if (parts.Length > 1)
{
return uint.Parse(parts[1].Replace(",", "").Replace(".", "").Trim());
return uint.Parse(
parts[1]
.Replace(",", "", StringComparison.Ordinal)
.Replace(".", "", StringComparison.Ordinal)
.Trim(),
CultureInfo.InvariantCulture);
}
}

View File

@ -13,7 +13,9 @@ partial class WorkshopPlugin
_pluginLog.Verbose("SelectYesNo post-setup");
AddonSelectYesno* addonSelectYesNo = (AddonSelectYesno*)args.Addon;
string text = MemoryHelper.ReadSeString(&addonSelectYesNo->PromptText->NodeText).ToString().Replace("\n", "").Replace("\r", "");
string text = MemoryHelper.ReadSeString(&addonSelectYesNo->PromptText->NodeText).ToString()
.Replace("\n", "", StringComparison.Ordinal)
.Replace("\r", "", StringComparison.Ordinal);
_pluginLog.Verbose($"YesNo prompt: '{text}'");
if (_repairKitWindow.IsOpen)

View File

@ -15,7 +15,7 @@ using Workshoppa.Windows;
namespace Workshoppa;
[SuppressMessage("ReSharper", "UnusedType.Global")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public sealed partial class WorkshopPlugin : IDalamudPlugin
{
private readonly IReadOnlyList<uint> _fabricationStationIds =

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<Version>4.3</Version>
<TargetFramework>net8.0-windows</TargetFramework>
<Version>5.0</Version>
<LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -1,7 +1,7 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"net8.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.12, )",

View File

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