Make EQP swaps also swap multi-slot items correctly.

This commit is contained in:
Ottermandias 2025-02-15 16:45:46 +01:00
parent f89eab8b2b
commit 2be5bd0611
4 changed files with 93 additions and 57 deletions

@ -1 +1 @@
Subproject commit 4a987167b665184d4c05fc9863993981c35a1d19 Subproject commit f6dff467c7dad6b1213a7d7b65d40a56450f0672

View file

@ -27,7 +27,7 @@ public static class EquipmentSwap
: []; : [];
} }
public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps, public static HashSet<EquipItem> CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps,
Func<Utf8GamePath, FullPath> redirections, MetaDictionary manips, Func<Utf8GamePath, FullPath> redirections, MetaDictionary manips,
EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo) EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo)
{ {
@ -88,7 +88,7 @@ public static class EquipmentSwap
return affectedItems; return affectedItems;
} }
public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps, public static HashSet<EquipItem> CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps,
Func<Utf8GamePath, FullPath> redirections, MetaDictionary manips, EquipItem itemFrom, Func<Utf8GamePath, FullPath> redirections, MetaDictionary manips, EquipItem itemFrom,
EquipItem itemTo, bool rFinger = true, bool lFinger = true) EquipItem itemTo, bool rFinger = true, bool lFinger = true)
{ {
@ -98,18 +98,28 @@ public static class EquipmentSwap
if (slotFrom != slotTo) if (slotFrom != slotTo)
throw new ItemSwap.InvalidItemTypeException(); throw new ItemSwap.InvalidItemTypeException();
HashSet<EquipItem> affectedItems = [];
var eqp = CreateEqp(manager, manips, slotFrom, idFrom, idTo); var eqp = CreateEqp(manager, manips, slotFrom, idFrom, idTo);
if (eqp != null) if (eqp != null)
{
swaps.Add(eqp); swaps.Add(eqp);
// Add items affected through multi-slot EQP edits.
foreach (var child in eqp.ChildSwaps.SelectMany(c => c.WithChildren()).OfType<MetaSwap<EqpIdentifier, EqpEntryInternal>>())
{
affectedItems.UnionWith(identifier
.Identify(GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, child.SwapFromIdentifier.Slot))
.Select(kvp => kvp.Value).OfType<IdentifiedItem>().Select(i => i.Item));
}
}
var gmp = CreateGmp(manager, manips, slotFrom, idFrom, idTo); var gmp = CreateGmp(manager, manips, slotFrom, idFrom, idTo);
if (gmp != null) if (gmp != null)
swaps.Add(gmp); swaps.Add(gmp);
var affectedItems = Array.Empty<EquipItem>();
foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger)) foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger))
{ {
(var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom); var (imcFileFrom, variants, affectedItemsLocal) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom);
affectedItems.UnionWith(affectedItemsLocal);
var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo); var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo);
var imcFileTo = new ImcFile(manager, imcIdentifierTo); var imcFileTo = new ImcFile(manager, imcIdentifierTo);
var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry) var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry)
@ -176,6 +186,7 @@ public static class EquipmentSwap
return affectedItems; return affectedItems;
} }
public static MetaSwap<EqdpIdentifier, EqdpEntryInternal>? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, public static MetaSwap<EqdpIdentifier, EqdpEntryInternal>? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
MetaDictionary manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo) MetaDictionary manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo)
=> CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo); => CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo);
@ -238,16 +249,17 @@ public static class EquipmentSwap
variant = i.Variant; variant = i.Variant;
} }
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, ObjectIdentification identifier, EquipSlot slotFrom, private static (ImcFile, Variant[], HashSet<EquipItem>) GetVariants(MetaFileManager manager, ObjectIdentification identifier,
EquipSlot slotFrom,
PrimaryId idFrom, PrimaryId idTo, Variant variantFrom) PrimaryId idFrom, PrimaryId idTo, Variant variantFrom)
{ {
var ident = new ImcIdentifier(slotFrom, idFrom, variantFrom); var ident = new ImcIdentifier(slotFrom, idFrom, variantFrom);
var imc = new ImcFile(manager, ident); var imc = new ImcFile(manager, ident);
EquipItem[] items; HashSet<EquipItem> items;
Variant[] variants; Variant[] variants;
if (idFrom == idTo) if (idFrom == idTo)
{ {
items = identifier.Identify(idFrom, 0, variantFrom, slotFrom).ToArray(); items = identifier.Identify(idFrom, 0, variantFrom, slotFrom).ToHashSet();
variants = [variantFrom]; variants = [variantFrom];
} }
else else
@ -256,7 +268,7 @@ public static class EquipmentSwap
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom) ? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)) : GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom))
.Select(kvp => kvp.Value).OfType<IdentifiedItem>().Select(i => i.Item) .Select(kvp => kvp.Value).OfType<IdentifiedItem>().Select(i => i.Item)
.ToArray(); .ToHashSet();
variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray(); variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray();
} }
@ -339,8 +351,8 @@ public static class EquipmentSwap
return avfx; return avfx;
} }
public static MetaSwap<EqpIdentifier, EqpEntryInternal>? CreateEqp(MetaFileManager manager, MetaDictionary manips, public static MetaSwap<EqpIdentifier, EqpEntryInternal>? CreateEqp(MetaFileManager manager, MetaDictionary manips, EquipSlot slot,
EquipSlot slot, PrimaryId idFrom, PrimaryId idTo) PrimaryId idFrom, PrimaryId idTo)
{ {
if (slot.IsAccessory()) if (slot.IsAccessory())
return null; return null;
@ -349,8 +361,32 @@ public static class EquipmentSwap
var manipToIdentifier = new EqpIdentifier(idTo, slot); var manipToIdentifier = new EqpIdentifier(idTo, slot);
var manipFromDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idFrom), slot); var manipFromDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idFrom), slot);
var manipToDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idTo), slot); var manipToDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idTo), slot);
return new MetaSwap<EqpIdentifier, EqpEntryInternal>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, var swap = new MetaSwap<EqpIdentifier, EqpEntryInternal>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier,
manipFromDefault, manipToIdentifier, manipToDefault); manipFromDefault, manipToIdentifier, manipToDefault);
var entry = swap.SwapToModdedEntry.ToEntry(slot);
// Add additional EQP entries if the swapped item is a multi-slot item,
// because those take the EQP entries of their other model-set slots when used.
switch (slot)
{
case EquipSlot.Body:
if (!entry.HasFlag(EqpEntry.BodyShowLeg)
&& CreateEqp(manager, manips, EquipSlot.Legs, idFrom, idTo) is { } legChild)
swap.ChildSwaps.Add(legChild);
if (!entry.HasFlag(EqpEntry.BodyShowHead)
&& CreateEqp(manager, manips, EquipSlot.Head, idFrom, idTo) is { } headChild)
swap.ChildSwaps.Add(headChild);
if (!entry.HasFlag(EqpEntry.BodyShowHand)
&& CreateEqp(manager, manips, EquipSlot.Hands, idFrom, idTo) is { } handChild)
swap.ChildSwaps.Add(handChild);
break;
case EquipSlot.Legs:
if (!entry.HasFlag(EqpEntry.LegsShowFoot)
&& CreateEqp(manager, manips, EquipSlot.Feet, idFrom, idTo) is { } footChild)
swap.ChildSwaps.Add(footChild);
break;
}
return swap;
} }
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, PrimaryId idFrom, public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, PrimaryId idFrom,

View file

@ -127,7 +127,7 @@ public class ItemSwapContainer
? new MetaDictionary(cache) ? new MetaDictionary(cache)
: _appliedModData.Manipulations; : _appliedModData.Manipulations;
public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, public HashSet<EquipItem> LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true,
bool useLeftRing = true) bool useLeftRing = true)
{ {
Swaps.Clear(); Swaps.Clear();
@ -138,7 +138,7 @@ public class ItemSwapContainer
return ret; return ret;
} }
public EquipItem[] LoadTypeSwap(EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null) public HashSet<EquipItem> LoadTypeSwap(EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null)
{ {
Swaps.Clear(); Swaps.Clear();
Loaded = false; Loaded = false;

View file

@ -180,7 +180,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService
private bool _useLeftRing = true; private bool _useLeftRing = true;
private bool _useRightRing = true; private bool _useRightRing = true;
private EquipItem[]? _affectedItems; private HashSet<EquipItem>? _affectedItems;
private void UpdateState() private void UpdateState()
{ {
@ -541,11 +541,11 @@ public class ItemSwapTab : IDisposable, ITab, IUiService
_dirty |= selector.Draw("##itemTarget", selector.CurrentSelection.Item.Name, string.Empty, InputWidth * 2 * UiHelpers.Scale, _dirty |= selector.Draw("##itemTarget", selector.CurrentSelection.Item.Name, string.Empty, InputWidth * 2 * UiHelpers.Scale,
ImGui.GetTextLineHeightWithSpacing()); ImGui.GetTextLineHeightWithSpacing());
if (_affectedItems is not { Length: > 1 }) if (_affectedItems is not { Count: > 1 })
return; return;
ImGui.SameLine(); ImGui.SameLine();
ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero, ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Count - 1} other Items.", Vector2.Zero,
Colors.PressEnterWarningBg); Colors.PressEnterWarningBg);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i.Name, selector.CurrentSelection.Item.Name)) ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i.Name, selector.CurrentSelection.Item.Name))
@ -602,11 +602,11 @@ public class ItemSwapTab : IDisposable, ITab, IUiService
_dirty |= ImGui.Checkbox("Swap Left Ring", ref _useLeftRing); _dirty |= ImGui.Checkbox("Swap Left Ring", ref _useLeftRing);
} }
if (_affectedItems is not { Length: > 1 }) if (_affectedItems is not { Count: > 1 })
return; return;
ImGui.SameLine(); ImGui.SameLine();
ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero, ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Count - 1} other Items.", Vector2.Zero,
Colors.PressEnterWarningBg); Colors.PressEnterWarningBg);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i.Name, targetSelector.CurrentSelection.Item.Name)) ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i.Name, targetSelector.CurrentSelection.Item.Name))