Add filtering mods by changed item categories.

This commit is contained in:
Ottermandias 2023-12-22 14:22:03 +01:00
parent 2051197c65
commit 4aa19e49d5
5 changed files with 89 additions and 34 deletions

@ -1 +1 @@
Subproject commit 3787e82d1b84d2542b6e4238060d75383a4b12a1
Subproject commit 58a3e7947c207452f5fa0d328c47c5ed6bdd9a0f

View file

@ -242,7 +242,7 @@ public static class EquipmentSwap
if (!slot.IsEquipmentPiece())
throw new ItemSwap.InvalidItemTypeException();
modelId = i.ModelId;
modelId = i.PrimaryId;
variant = i.Variant;
}

View file

@ -43,8 +43,71 @@ public class ChangedItemDrawer : IDisposable
Emote = 0x01_00_00,
}
public const ChangedItemIcon AllFlags = (ChangedItemIcon)0x01FFFF;
public const ChangedItemIcon DefaultFlags = AllFlags & ~ChangedItemIcon.Offhand;
private static readonly ChangedItemIcon[] Order =
[
ChangedItemIcon.Head,
ChangedItemIcon.Body,
ChangedItemIcon.Hands,
ChangedItemIcon.Legs,
ChangedItemIcon.Feet,
ChangedItemIcon.Ears,
ChangedItemIcon.Neck,
ChangedItemIcon.Wrists,
ChangedItemIcon.Finger,
ChangedItemIcon.Mainhand,
ChangedItemIcon.Offhand,
ChangedItemIcon.Customization,
ChangedItemIcon.Action,
ChangedItemIcon.Emote,
ChangedItemIcon.Monster,
ChangedItemIcon.Demihuman,
ChangedItemIcon.Unknown,
];
private static readonly string[] LowerNames = Order.Select(f => ToDescription(f).ToLowerInvariant()).ToArray();
public static bool TryParseIndex(ReadOnlySpan<char> input, out ChangedItemIcon slot)
{
// Handle numeric cases before TryParse because numbers
// are not logical otherwise.
if (int.TryParse(input, out var idx))
{
// We assume users will use 1-based index, but if they enter 0, just use the first.
if (idx == 0)
{
slot = Order[0];
return true;
}
// Use 1-based index.
--idx;
if (idx >= 0 && idx < Order.Length)
{
slot = Order[idx];
return true;
}
}
slot = 0;
return false;
}
public static bool TryParsePartial(string lowerInput, out ChangedItemIcon slot)
{
if (TryParseIndex(lowerInput, out slot))
return true;
slot = 0;
foreach (var (item, flag) in LowerNames.Zip(Order))
if (item.Contains(lowerInput, StringComparison.Ordinal))
slot |= flag;
return slot != 0;
}
public const ChangedItemIcon AllFlags = (ChangedItemIcon)0x01FFFF;
public static readonly int NumCategories = Order.Length;
public const ChangedItemIcon DefaultFlags = AllFlags & ~ChangedItemIcon.Offhand;
private readonly Configuration _config;
private readonly ExcelSheet<Item> _items;
@ -163,26 +226,7 @@ public class ChangedItemDrawer : IDisposable
using var _ = ImRaii.PushId("ChangedItemIconFilter");
var size = TypeFilterIconSize;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
var order = new[]
{
ChangedItemIcon.Head,
ChangedItemIcon.Body,
ChangedItemIcon.Hands,
ChangedItemIcon.Legs,
ChangedItemIcon.Feet,
ChangedItemIcon.Ears,
ChangedItemIcon.Neck,
ChangedItemIcon.Wrists,
ChangedItemIcon.Finger,
ChangedItemIcon.Mainhand,
ChangedItemIcon.Offhand,
ChangedItemIcon.Customization,
ChangedItemIcon.Action,
ChangedItemIcon.Emote,
ChangedItemIcon.Monster,
ChangedItemIcon.Demihuman,
ChangedItemIcon.Unknown,
};
bool DrawIcon(ChangedItemIcon type, ref ChangedItemIcon typeFilter)
{
@ -217,13 +261,13 @@ public class ChangedItemDrawer : IDisposable
return ret;
}
foreach (var iconType in order)
foreach (var iconType in Order)
{
ret |= DrawIcon(iconType, ref typeFilter);
ImGui.SameLine();
}
ImGui.SetCursorPos(new(ImGui.GetContentRegionMax().X - size.X, ImGui.GetCursorPosY() + yOffset));
ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionMax().X - size.X, ImGui.GetCursorPosY() + yOffset));
ImGui.Image(_icons[AllFlags].ImGuiHandle, size, Vector2.Zero, Vector2.One,
typeFilter == 0 ? new Vector4(0.6f, 0.3f, 0.3f, 1f) :
typeFilter == AllFlags ? new Vector4(0.75f, 0.75f, 0.75f, 1f) : new Vector4(0.5f, 0.5f, 1f, 1f));

View file

@ -126,7 +126,7 @@ public class IndividualAssignmentUi : IDisposable
/// <summary> Create combos when ready. </summary>
private void SetupCombos()
{
_worldCombo = new WorldCombo(_actors.Data.Worlds, Penumbra.Log, WorldId.AnyWorld);
_worldCombo = new WorldCombo(_actors.Data.Worlds, Penumbra.Log);
_mountCombo = new NpcCombo("##mountCombo", _actors.Data.Mounts, Penumbra.Log);
_companionCombo = new NpcCombo("##companionCombo", _actors.Data.Companions, Penumbra.Log);
_ornamentCombo = new NpcCombo("##ornamentCombo", _actors.Data.Ornaments, Penumbra.Log);

View file

@ -12,6 +12,8 @@ using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
@ -190,7 +192,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
var itemPos = ImGui.GetItemRectMax().X;
var maxWidth = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
var priorityString = $"[{state.Priority}]";
var Size = ImGui.CalcTextSize(priorityString).X;
var Size = ImGui.CalcTextSize(priorityString).X;
var remainingSpace = maxWidth - itemPos;
var offset = remainingSpace - Size;
if (ImGui.GetScrollMaxY() == 0)
@ -507,10 +509,11 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
public int Priority;
}
private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
private LowerString _modFilter = LowerString.Empty;
private int _filterType = -1;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
private LowerString _modFilter = LowerString.Empty;
private int _filterType = -1;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
private ChangedItemDrawer.ChangedItemIcon _slotFilter = 0;
private void SetFilterTooltip()
{
@ -518,7 +521,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
+ "Enter c:[string] to filter for mods changing specific items.\n"
+ "Enter t:[string] to filter for mods set to specific tags.\n"
+ "Enter n:[string] to filter only for mod names and no paths.\n"
+ "Enter a:[string] to filter for mods by specific authors.\n\n"
+ "Enter a:[string] to filter for mods by specific authors.\n"
+ $"Enter s:[string] to filter for mods by the categories of the items they change (1-{ChangedItemDrawer.NumCategories+1} or partial category name).\n"
+ "Use None as a placeholder value that only matches empty lists or names.";
}
@ -539,6 +543,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3),
't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4),
'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4),
's' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 5),
'S' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 5),
_ => (new LowerString(filterValue), 0),
},
_ => (new LowerString(filterValue), 0),
@ -549,10 +555,13 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
private const int EmptyOffset = 128;
private static (LowerString, int) ParseFilter(string value, int id)
private (LowerString, int) ParseFilter(string value, int id)
{
value = value[2..];
var lower = new LowerString(value);
if (id == 5 && !ChangedItemDrawer.TryParsePartial(lower.Lower, out _slotFilter))
_slotFilter = 0;
return (lower, lower.Lower is "none" ? id + EmptyOffset : id);
}
@ -602,9 +611,11 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
2 => !mod.Author.Contains(_modFilter),
3 => !mod.LowerChangedItemsString.Contains(_modFilter.Lower),
4 => !mod.AllTagsLower.Contains(_modFilter.Lower),
5 => mod.ChangedItems.All(p => (ChangedItemDrawer.GetCategoryIcon(p.Key, p.Value) & _slotFilter) == 0),
2 + EmptyOffset => !mod.Author.IsEmpty,
3 + EmptyOffset => mod.LowerChangedItemsString.Length > 0,
4 + EmptyOffset => mod.AllTagsLower.Length > 0,
5 + EmptyOffset => mod.ChangedItems.Count == 0,
_ => false, // Should never happen
};
}