This commit is contained in:
Ottermandias 2024-06-11 12:23:08 +02:00
parent 196ca2ce39
commit 361082813b
16 changed files with 1008 additions and 660 deletions

View file

@ -7,7 +7,7 @@ using ImcEntry = Penumbra.GameData.Structs.ImcEntry;
namespace Penumbra.Meta.Manipulations;
[JsonConverter(typeof(Converter))]
public sealed class MetaDictionary : IEnumerable<MetaManipulation>
public class MetaDictionary : IEnumerable<MetaManipulation>
{
private readonly Dictionary<ImcIdentifier, ImcEntry> _imc = [];
private readonly Dictionary<EqpIdentifier, EqpEntryInternal> _eqp = [];
@ -17,8 +17,37 @@ public sealed class MetaDictionary : IEnumerable<MetaManipulation>
private readonly Dictionary<GmpIdentifier, GmpEntry> _gmp = [];
private readonly HashSet<GlobalEqpManipulation> _globalEqp = [];
public IReadOnlyDictionary<ImcIdentifier, ImcEntry> Imc
=> _imc;
public int Count { get; private set; }
public int GetCount(MetaManipulation.Type type)
=> type switch
{
MetaManipulation.Type.Imc => _imc.Count,
MetaManipulation.Type.Eqdp => _eqdp.Count,
MetaManipulation.Type.Eqp => _eqp.Count,
MetaManipulation.Type.Est => _est.Count,
MetaManipulation.Type.Gmp => _gmp.Count,
MetaManipulation.Type.Rsp => _rsp.Count,
MetaManipulation.Type.GlobalEqp => _globalEqp.Count,
_ => 0,
};
public bool CanAdd(IMetaIdentifier identifier)
=> identifier switch
{
EqdpIdentifier eqdpIdentifier => !_eqdp.ContainsKey(eqdpIdentifier),
EqpIdentifier eqpIdentifier => !_eqp.ContainsKey(eqpIdentifier),
EstIdentifier estIdentifier => !_est.ContainsKey(estIdentifier),
GlobalEqpManipulation globalEqpManipulation => !_globalEqp.Contains(globalEqpManipulation),
GmpIdentifier gmpIdentifier => !_gmp.ContainsKey(gmpIdentifier),
ImcIdentifier imcIdentifier => !_imc.ContainsKey(imcIdentifier),
RspIdentifier rspIdentifier => !_rsp.ContainsKey(rspIdentifier),
_ => false,
};
public void Clear()
{
_imc.Clear();
@ -123,6 +152,68 @@ public sealed class MetaDictionary : IEnumerable<MetaManipulation>
return true;
}
public bool Update(ImcIdentifier identifier, ImcEntry entry)
{
if (!_imc.ContainsKey(identifier))
return false;
_imc[identifier] = entry;
return true;
}
public bool Update(EqpIdentifier identifier, EqpEntryInternal entry)
{
if (!_eqp.ContainsKey(identifier))
return false;
_eqp[identifier] = entry;
return true;
}
public bool Update(EqpIdentifier identifier, EqpEntry entry)
=> Update(identifier, new EqpEntryInternal(entry, identifier.Slot));
public bool Update(EqdpIdentifier identifier, EqdpEntryInternal entry)
{
if (!_eqdp.ContainsKey(identifier))
return false;
_eqdp[identifier] = entry;
return true;
}
public bool Update(EqdpIdentifier identifier, EqdpEntry entry)
=> Update(identifier, new EqdpEntryInternal(entry, identifier.Slot));
public bool Update(EstIdentifier identifier, EstEntry entry)
{
if (!_est.ContainsKey(identifier))
return false;
_est[identifier] = entry;
return true;
}
public bool Update(GmpIdentifier identifier, GmpEntry entry)
{
if (!_gmp.ContainsKey(identifier))
return false;
_gmp[identifier] = entry;
return true;
}
public bool Update(RspIdentifier identifier, RspEntry entry)
{
if (!_rsp.ContainsKey(identifier))
return false;
_rsp[identifier] = entry;
return true;
}
public void UnionWith(MetaDictionary manips)
{
foreach (var (identifier, entry) in manips._imc)
@ -148,70 +239,52 @@ public sealed class MetaDictionary : IEnumerable<MetaManipulation>
}
/// <summary> Try to merge all manipulations from manips into this, and return the first failure, if any. </summary>
public bool MergeForced(MetaDictionary manips, out IMetaIdentifier failedIdentifier)
public bool MergeForced(MetaDictionary manips, out IMetaIdentifier? failedIdentifier)
{
foreach (var (identifier, entry) in manips._imc)
foreach (var (identifier, _) in manips._imc.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
{
if (!TryAdd(identifier, entry))
{
failedIdentifier = identifier;
return false;
}
failedIdentifier = identifier;
return false;
}
foreach (var (identifier, entry) in manips._eqp)
foreach (var (identifier, _) in manips._eqp.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
{
if (!TryAdd(identifier, entry))
{
failedIdentifier = identifier;
return false;
}
failedIdentifier = identifier;
return false;
}
foreach (var (identifier, entry) in manips._eqdp)
foreach (var (identifier, _) in manips._eqdp.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
{
if (!TryAdd(identifier, entry))
{
failedIdentifier = identifier;
return false;
}
failedIdentifier = identifier;
return false;
}
foreach (var (identifier, entry) in manips._gmp)
foreach (var (identifier, _) in manips._gmp.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
{
if (!TryAdd(identifier, entry))
{
failedIdentifier = identifier;
return false;
}
failedIdentifier = identifier;
return false;
}
foreach (var (identifier, entry) in manips._rsp)
foreach (var (identifier, _) in manips._rsp.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
{
if (!TryAdd(identifier, entry))
{
failedIdentifier = identifier;
return false;
}
failedIdentifier = identifier;
return false;
}
foreach (var (identifier, entry) in manips._est)
foreach (var (identifier, _) in manips._est.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
{
if (!TryAdd(identifier, entry))
{
failedIdentifier = identifier;
return false;
}
failedIdentifier = identifier;
return false;
}
foreach (var identifier in manips._globalEqp)
foreach (var identifier in manips._globalEqp.Where(identifier => !TryAdd(identifier)))
{
if (!TryAdd(identifier))
{
failedIdentifier = identifier;
return false;
}
failedIdentifier = identifier;
return false;
}
failedIdentifier = default;
return false;
}
public bool TryGetValue(EstIdentifier identifier, out EstEntry value)
@ -244,6 +317,18 @@ public sealed class MetaDictionary : IEnumerable<MetaManipulation>
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count;
}
public void UpdateTo(MetaDictionary other)
{
_imc.UpdateTo(other._imc);
_eqp.UpdateTo(other._eqp);
_eqdp.UpdateTo(other._eqdp);
_est.UpdateTo(other._est);
_rsp.UpdateTo(other._rsp);
_gmp.UpdateTo(other._gmp);
_globalEqp.UnionWith(other._globalEqp);
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count;
}
public MetaDictionary Clone()
{
var ret = new MetaDictionary();
@ -251,6 +336,31 @@ public sealed class MetaDictionary : IEnumerable<MetaManipulation>
return ret;
}
private static void WriteJson(JsonWriter writer, JsonSerializer serializer, IMetaIdentifier identifier, object entry)
{
var type = identifier switch
{
ImcIdentifier => "Imc",
EqdpIdentifier => "Eqdp",
EqpIdentifier => "Eqp",
EstIdentifier => "Est",
GmpIdentifier => "Gmp",
RspIdentifier => "Rsp",
GlobalEqpManipulation => "GlobalEqp",
_ => string.Empty,
};
if (type.Length == 0)
return;
writer.WriteStartObject();
writer.WritePropertyName("Type");
writer.WriteValue(type);
writer.WritePropertyName("Manipulation");
writer.WriteEndObject();
}
private class Converter : JsonConverter<MetaDictionary>
{
public override void WriteJson(JsonWriter writer, MetaDictionary? value, JsonSerializer serializer)

View file

@ -1,9 +1,148 @@
using Dalamud.Interface;
using ImGuiNET;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.Mods.Editor;
using Penumbra.String.Functions;
using Penumbra.UI;
using Penumbra.UI.ModsTab;
namespace Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Manipulations;
#if false
private static class ImcRow
{
private static ImcIdentifier _newIdentifier = ImcIdentifier.Default;
private static float IdWidth
=> 80 * UiHelpers.Scale;
private static float SmallIdWidth
=> 45 * UiHelpers.Scale;
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
{
ImGui.TableNextColumn();
CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize,
editor.MetaEditor.Imc.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var (defaultEntry, fileExists, _) = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true);
var manip = (MetaManipulation)new ImcManipulation(_newIdentifier, defaultEntry);
var canAdd = fileExists && editor.MetaEditor.CanAdd(manip);
var tt = canAdd ? "Stage this edit." : !fileExists ? "This IMC file does not exist." : "This entry is already edited.";
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
editor.MetaEditor.Add(manip);
// Identifier
ImGui.TableNextColumn();
var change = ImcManipulationDrawer.DrawObjectType(ref _newIdentifier);
ImGui.TableNextColumn();
change |= ImcManipulationDrawer.DrawPrimaryId(ref _newIdentifier);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
ImGui.TableNextColumn();
// Equipment and accessories are slightly different imcs than other types.
if (_newIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier);
else
change |= ImcManipulationDrawer.DrawSecondaryId(ref _newIdentifier);
ImGui.TableNextColumn();
change |= ImcManipulationDrawer.DrawVariant(ref _newIdentifier);
ImGui.TableNextColumn();
if (_newIdentifier.ObjectType is ObjectType.DemiHuman)
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier, 70);
else
ImUtf8.ScaledDummy(new Vector2(70 * UiHelpers.Scale, 0));
if (change)
defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true).Entry;
// Values
using var disabled = ImRaii.Disabled();
ImGui.TableNextColumn();
ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref defaultEntry, false);
ImGui.SameLine();
ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref defaultEntry, false);
ImGui.TableNextColumn();
ImcManipulationDrawer.DrawDecalId(defaultEntry, ref defaultEntry, false);
ImGui.SameLine();
ImcManipulationDrawer.DrawVfxId(defaultEntry, ref defaultEntry, false);
ImGui.SameLine();
ImcManipulationDrawer.DrawSoundId(defaultEntry, ref defaultEntry, false);
ImGui.TableNextColumn();
ImcManipulationDrawer.DrawAttributes(defaultEntry, ref defaultEntry);
}
public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize)
{
DrawMetaButtons(meta, editor, iconSize);
// Identifier
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
ImGui.TextUnformatted(meta.ObjectType.ToName());
ImGuiUtil.HoverTooltip(ObjectTypeTooltip);
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
ImGui.TextUnformatted(meta.PrimaryId.ToString());
ImGuiUtil.HoverTooltip(PrimaryIdTooltipShort);
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
if (meta.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
{
ImGui.TextUnformatted(meta.EquipSlot.ToName());
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
}
else
{
ImGui.TextUnformatted(meta.SecondaryId.ToString());
ImGuiUtil.HoverTooltip(SecondaryIdTooltip);
}
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
ImGui.TextUnformatted(meta.Variant.ToString());
ImGuiUtil.HoverTooltip(VariantIdTooltip);
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
if (meta.ObjectType is ObjectType.DemiHuman)
{
ImGui.TextUnformatted(meta.EquipSlot.ToName());
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
}
// Values
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
ImGui.TableNextColumn();
var defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(meta.Identifier, true).Entry;
var newEntry = meta.Entry;
var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true);
ImGui.SameLine();
changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true);
ImGui.TableNextColumn();
changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref newEntry, true);
ImGui.SameLine();
changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref newEntry, true);
ImGui.SameLine();
changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref newEntry, true);
ImGui.TableNextColumn();
changes |= ImcManipulationDrawer.DrawAttributes(defaultEntry, ref newEntry);
if (changes)
editor.MetaEditor.Change(meta.Copy(newEntry));
}
}
#endif
public interface IMetaManipulation
{
@ -315,3 +454,4 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
public static bool operator >=(MetaManipulation left, MetaManipulation right)
=> left.CompareTo(right) >= 0;
}

View file

@ -162,12 +162,9 @@ public class ModMerger : IDisposable
foreach (var originalOption in mergeOptions)
{
foreach (var manip in originalOption.Manipulations)
{
if (!manips.Add(manip))
throw new Exception(
$"Could not add meta manipulation {manip} from {originalOption.GetFullName()} to {option.GetFullName()} because another manipulation of the same data already exists in this option.");
}
if (!manips.MergeForced(originalOption.Manipulations, out var failed))
throw new Exception(
$"Could not add meta manipulation {failed} from {originalOption.GetFullName()} to {option.GetFullName()} because another manipulation of the same data already exists in this option.");
foreach (var (swapA, swapB) in originalOption.FileSwaps)
{

View file

@ -1,24 +1,24 @@
using System.Collections.Frozen;
using OtterGui.Services;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Manager;
using Penumbra.Mods.SubMods;
namespace Penumbra.Mods.Editor;
public class ModMetaEditor(ModManager modManager)
public class ModMetaEditor(ModManager modManager) : MetaDictionary, IService
{
private readonly HashSet<ImcManipulation> _imc = [];
private readonly HashSet<EqpManipulation> _eqp = [];
private readonly HashSet<EqdpManipulation> _eqdp = [];
private readonly HashSet<GmpManipulation> _gmp = [];
private readonly HashSet<EstManipulation> _est = [];
private readonly HashSet<RspManipulation> _rsp = [];
private readonly HashSet<GlobalEqpManipulation> _globalEqp = [];
public sealed class OtherOptionData : HashSet<string>
{
public int TotalCount;
public void Add(string name, int count)
{
if (count > 0)
Add(name);
TotalCount += count;
}
public new void Clear()
{
TotalCount = 0;
@ -31,91 +31,9 @@ public class ModMetaEditor(ModManager modManager)
public bool Changes { get; private set; }
public IReadOnlySet<ImcManipulation> Imc
=> _imc;
public IReadOnlySet<EqpManipulation> Eqp
=> _eqp;
public IReadOnlySet<EqdpManipulation> Eqdp
=> _eqdp;
public IReadOnlySet<GmpManipulation> Gmp
=> _gmp;
public IReadOnlySet<EstManipulation> Est
=> _est;
public IReadOnlySet<RspManipulation> Rsp
=> _rsp;
public IReadOnlySet<GlobalEqpManipulation> GlobalEqp
=> _globalEqp;
public bool CanAdd(MetaManipulation m)
public new void Clear()
{
return m.ManipulationType switch
{
MetaManipulation.Type.Imc => !_imc.Contains(m.Imc),
MetaManipulation.Type.Eqdp => !_eqdp.Contains(m.Eqdp),
MetaManipulation.Type.Eqp => !_eqp.Contains(m.Eqp),
MetaManipulation.Type.Est => !_est.Contains(m.Est),
MetaManipulation.Type.Gmp => !_gmp.Contains(m.Gmp),
MetaManipulation.Type.Rsp => !_rsp.Contains(m.Rsp),
MetaManipulation.Type.GlobalEqp => !_globalEqp.Contains(m.GlobalEqp),
_ => false,
};
}
public bool Add(MetaManipulation m)
{
var added = m.ManipulationType switch
{
MetaManipulation.Type.Imc => _imc.Add(m.Imc),
MetaManipulation.Type.Eqdp => _eqdp.Add(m.Eqdp),
MetaManipulation.Type.Eqp => _eqp.Add(m.Eqp),
MetaManipulation.Type.Est => _est.Add(m.Est),
MetaManipulation.Type.Gmp => _gmp.Add(m.Gmp),
MetaManipulation.Type.Rsp => _rsp.Add(m.Rsp),
MetaManipulation.Type.GlobalEqp => _globalEqp.Add(m.GlobalEqp),
_ => false,
};
Changes |= added;
return added;
}
public bool Delete(MetaManipulation m)
{
var deleted = m.ManipulationType switch
{
MetaManipulation.Type.Imc => _imc.Remove(m.Imc),
MetaManipulation.Type.Eqdp => _eqdp.Remove(m.Eqdp),
MetaManipulation.Type.Eqp => _eqp.Remove(m.Eqp),
MetaManipulation.Type.Est => _est.Remove(m.Est),
MetaManipulation.Type.Gmp => _gmp.Remove(m.Gmp),
MetaManipulation.Type.Rsp => _rsp.Remove(m.Rsp),
MetaManipulation.Type.GlobalEqp => _globalEqp.Remove(m.GlobalEqp),
_ => false,
};
Changes |= deleted;
return deleted;
}
public bool Change(MetaManipulation m)
=> Delete(m) && Add(m);
public bool Set(MetaManipulation m)
=> Delete(m) | Add(m);
public void Clear()
{
_imc.Clear();
_eqp.Clear();
_eqdp.Clear();
_gmp.Clear();
_est.Clear();
_rsp.Clear();
_globalEqp.Clear();
base.Clear();
Changes = true;
}
@ -129,15 +47,19 @@ public class ModMetaEditor(ModManager modManager)
if (option == currentOption)
continue;
foreach (var manip in option.Manipulations)
{
var data = OtherData[manip.ManipulationType];
++data.TotalCount;
data.Add(option.GetFullName());
}
var name = option.GetFullName();
OtherData[MetaManipulation.Type.Imc].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Imc));
OtherData[MetaManipulation.Type.Eqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqp));
OtherData[MetaManipulation.Type.Eqdp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqdp));
OtherData[MetaManipulation.Type.Gmp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Gmp));
OtherData[MetaManipulation.Type.Est].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Est));
OtherData[MetaManipulation.Type.Rsp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Rsp));
OtherData[MetaManipulation.Type.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.GlobalEqp));
}
Split(currentOption.Manipulations);
Clear();
UnionWith(currentOption.Manipulations);
Changes = false;
}
public void Apply(IModDataContainer container)
@ -145,50 +67,7 @@ public class ModMetaEditor(ModManager modManager)
if (!Changes)
return;
modManager.OptionEditor.SetManipulations(container, [..Recombine()]);
modManager.OptionEditor.SetManipulations(container, this);
Changes = false;
}
private void Split(IEnumerable<MetaManipulation> manips)
{
Clear();
foreach (var manip in manips)
{
switch (manip.ManipulationType)
{
case MetaManipulation.Type.Imc:
_imc.Add(manip.Imc);
break;
case MetaManipulation.Type.Eqdp:
_eqdp.Add(manip.Eqdp);
break;
case MetaManipulation.Type.Eqp:
_eqp.Add(manip.Eqp);
break;
case MetaManipulation.Type.Est:
_est.Add(manip.Est);
break;
case MetaManipulation.Type.Gmp:
_gmp.Add(manip.Gmp);
break;
case MetaManipulation.Type.Rsp:
_rsp.Add(manip.Rsp);
break;
case MetaManipulation.Type.GlobalEqp:
_globalEqp.Add(manip.GlobalEqp);
break;
}
}
Changes = false;
}
public IEnumerable<MetaManipulation> Recombine()
=> _imc.Select(m => (MetaManipulation)m)
.Concat(_eqdp.Select(m => (MetaManipulation)m))
.Concat(_eqp.Select(m => (MetaManipulation)m))
.Concat(_est.Select(m => (MetaManipulation)m))
.Concat(_gmp.Select(m => (MetaManipulation)m))
.Concat(_rsp.Select(m => (MetaManipulation)m))
.Concat(_globalEqp.Select(m => (MetaManipulation)m));
}

View file

@ -95,28 +95,28 @@ public class ImcModGroup(Mod mod) : IModGroup
public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer)
=> new ImcModGroupEditDrawer(editDrawer, this);
public ImcManipulation GetManip(ushort mask, Variant variant)
=> new(Identifier.ObjectType, Identifier.BodySlot, Identifier.PrimaryId, Identifier.SecondaryId.Id, variant.Id,
Identifier.EquipSlot, DefaultEntry with { AttributeMask = mask });
public ImcEntry GetEntry(ushort mask)
=> DefaultEntry with { AttributeMask = mask };
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, MetaDictionary manipulations)
{
if (IsDisabled(setting))
return;
var mask = GetCurrentMask(setting);
var mask = GetCurrentMask(setting);
var entry = GetEntry(mask);
if (AllVariants)
{
var count = ImcChecker.GetVariantCount(Identifier);
if (count == 0)
manipulations.Add(GetManip(mask, Identifier.Variant));
manipulations.TryAdd(Identifier, entry);
else
for (var i = 0; i <= count; ++i)
manipulations.Add(GetManip(mask, (Variant)i));
manipulations.TryAdd(Identifier with { Variant = (Variant)i }, entry);
}
else
{
manipulations.Add(GetManip(mask, Identifier.Variant));
manipulations.TryAdd(Identifier, entry);
}
}

View file

@ -124,7 +124,7 @@ public class ItemSwapContainer
private MetaDictionary MetaResolver(ModCollection? collection)
=> collection?.MetaCache?.Manipulations is { } cache
? [.. cache]
? [] // [.. cache] TODO
: _appliedModData.Manipulations;
public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true,

View file

@ -198,7 +198,8 @@ public partial class ModCreator(
Penumbra.Log.Verbose(
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
deleteList.Add(file.FullName);
option.Manipulations.UnionWith([.. meta.MetaManipulations]);
// TODO
option.Manipulations.UnionWith([]);//[.. meta.MetaManipulations]);
}
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
{
@ -212,7 +213,8 @@ public partial class ModCreator(
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
deleteList.Add(file.FullName);
option.Manipulations.UnionWith([.. rgsp.MetaManipulations]);
// TODO
option.Manipulations.UnionWith([]);//[.. rgsp.MetaManipulations]);
}
}
catch (Exception e)

View file

@ -37,7 +37,7 @@ public static class SubMod
{
to.Files = new Dictionary<Utf8GamePath, FullPath>(from.Files);
to.FileSwaps = new Dictionary<Utf8GamePath, FullPath>(from.FileSwaps);
to.Manipulations = [.. from.Manipulations];
to.Manipulations = from.Manipulations.Clone();
}
/// <summary> Load all file redirections, file swaps and meta manipulations from a JToken of that option into a data container. </summary>

View file

@ -93,7 +93,8 @@ public class TemporaryMod : IMod
}
}
MetaDictionary manips = [.. collection.MetaCache?.Manipulations ?? []];
// TODO
MetaDictionary manips = []; // [.. collection.MetaCache?.Manipulations ?? []];
defaultMod.Manipulations.UnionWith(manips);
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));

View file

@ -188,7 +188,6 @@ public static class StaticServiceManager
.AddSingleton<DuplicateManager>()
.AddSingleton<MdlMaterialEditor>()
.AddSingleton<ModFileEditor>()
.AddSingleton<ModMetaEditor>()
.AddSingleton<ModSwapEditor>()
.AddSingleton<ModNormalizer>()
.AddSingleton<ModMerger>()

View file

@ -0,0 +1,353 @@
using Dalamud.Interface;
using ImGuiNET;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.UI.Classes;
namespace Penumbra.UI.AdvancedWindow.Meta;
public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
: MetaDrawer<ImcIdentifier, ImcEntry>(editor, metaFiles), IService
{
private bool _fileExists;
private const string ModelSetIdTooltipShort = "Model Set ID";
private const string EquipSlotTooltip = "Equip Slot";
private const string ModelRaceTooltip = "Model Race";
private const string GenderTooltip = "Gender";
private const string ObjectTypeTooltip = "Object Type";
private const string SecondaryIdTooltip = "Secondary ID";
private const string PrimaryIdTooltipShort = "Primary ID";
private const string VariantIdTooltip = "Variant ID";
private const string EstTypeTooltip = "EST Type";
private const string RacialTribeTooltip = "Racial Tribe";
private const string ScalingTypeTooltip = "Scaling Type";
protected override void Initialize()
{
Identifier = ImcIdentifier.Default;
UpdateEntry();
}
private void UpdateEntry()
=> (Entry, _fileExists, _) = MetaFiles.ImcChecker.GetDefaultEntry(Identifier, true);
protected override void DrawNew()
{
ImGui.TableNextColumn();
// Copy To Clipboard
ImGui.TableNextColumn();
var canAdd = _fileExists && Editor.MetaEditor.CanAdd(Identifier);
var tt = canAdd ? "Stage this edit."u8 : !_fileExists ? "This IMC file does not exist."u8 : "This entry is already edited."u8;
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
Editor.MetaEditor.TryAdd(Identifier, Entry);
if (DrawIdentifier(ref Identifier))
UpdateEntry();
using var disabled = ImRaii.Disabled();
DrawEntry(Entry, ref Entry, false);
}
protected override void DrawEntry(ImcIdentifier identifier, ImcEntry entry)
{
const uint frameColor = 0;
// Meta Buttons
ImGui.TableNextColumn();
ImUtf8.TextFramed(identifier.ObjectType.ToName(), frameColor);
ImUtf8.HoverTooltip("Object Type"u8);
ImGui.TableNextColumn();
ImUtf8.TextFramed($"{identifier.PrimaryId.Id}", frameColor);
ImUtf8.HoverTooltip("Primary ID");
ImGui.TableNextColumn();
if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
{
ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor);
ImUtf8.HoverTooltip("Equip Slot"u8);
}
else
{
ImUtf8.TextFramed($"{identifier.SecondaryId.Id}", frameColor);
ImUtf8.HoverTooltip("Secondary ID"u8);
}
ImGui.TableNextColumn();
ImUtf8.TextFramed($"{identifier.Variant.Id}", frameColor);
ImUtf8.HoverTooltip("Variant"u8);
ImGui.TableNextColumn();
if (identifier.ObjectType is ObjectType.DemiHuman)
{
ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor);
ImUtf8.HoverTooltip("Equip Slot"u8);
}
var defaultEntry = MetaFiles.ImcChecker.GetDefaultEntry(identifier, true).Entry;
if (DrawEntry(defaultEntry, ref entry, true))
Editor.MetaEditor.Update(identifier, entry);
}
private static bool DrawIdentifier(ref ImcIdentifier identifier)
{
ImGui.TableNextColumn();
var change = DrawObjectType(ref identifier);
ImGui.TableNextColumn();
change |= DrawPrimaryId(ref identifier);
ImGui.TableNextColumn();
if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
change |= DrawSlot(ref identifier);
else
change |= DrawSecondaryId(ref identifier);
ImGui.TableNextColumn();
change |= DrawVariant(ref identifier);
ImGui.TableNextColumn();
if (identifier.ObjectType is ObjectType.DemiHuman)
change |= DrawSlot(ref identifier, 70f);
else
ImUtf8.ScaledDummy(70f);
return change;
}
private static bool DrawEntry(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault)
{
ImGui.TableNextColumn();
var change = DrawMaterialId(defaultEntry, ref entry, addDefault);
ImUtf8.SameLineInner();
change |= DrawMaterialAnimationId(defaultEntry, ref entry, addDefault);
ImGui.TableNextColumn();
change |= DrawDecalId(defaultEntry, ref entry, addDefault);
ImUtf8.SameLineInner();
change |= DrawVfxId(defaultEntry, ref entry, addDefault);
ImUtf8.SameLineInner();
change |= DrawSoundId(defaultEntry, ref entry, addDefault);
ImGui.TableNextColumn();
change |= DrawAttributes(defaultEntry, ref entry);
return change;
}
protected override IEnumerable<(ImcIdentifier, ImcEntry)> Enumerate()
=> Editor.MetaEditor.Imc.Select(kvp => (kvp.Key, kvp.Value));
public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110)
{
var ret = Combos.ImcType("##imcType", identifier.ObjectType, out var type, width);
ImUtf8.HoverTooltip("Object Type"u8);
if (ret)
{
var equipSlot = type switch
{
ObjectType.Equipment => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head,
ObjectType.DemiHuman => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head,
ObjectType.Accessory => identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears,
_ => EquipSlot.Unknown,
};
identifier = identifier with
{
ObjectType = type,
EquipSlot = equipSlot,
SecondaryId = identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId,
};
}
return ret;
}
public static bool DrawPrimaryId(ref ImcIdentifier identifier, float unscaledWidth = 80)
{
var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, identifier.PrimaryId.Id, out var newId, 0, ushort.MaxValue,
identifier.PrimaryId.Id <= 1);
ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8
+ "This should generally not be left <= 1 unless you explicitly want that."u8);
if (ret)
identifier = identifier with { PrimaryId = newId };
return ret;
}
public static bool DrawSecondaryId(ref ImcIdentifier identifier, float unscaledWidth = 100)
{
var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, identifier.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false);
ImUtf8.HoverTooltip("Secondary ID"u8);
if (ret)
identifier = identifier with { SecondaryId = newId };
return ret;
}
public static bool DrawVariant(ref ImcIdentifier identifier, float unscaledWidth = 45)
{
var ret = IdInput("##imcVariant"u8, unscaledWidth, identifier.Variant.Id, out var newId, 0, byte.MaxValue, false);
ImUtf8.HoverTooltip("Variant ID"u8);
if (ret)
identifier = identifier with { Variant = (byte)newId };
return ret;
}
public static bool DrawSlot(ref ImcIdentifier identifier, float unscaledWidth = 100)
{
bool ret;
EquipSlot slot;
switch (identifier.ObjectType)
{
case ObjectType.Equipment:
case ObjectType.DemiHuman:
ret = Combos.EqpEquipSlot("##slot", identifier.EquipSlot, out slot, unscaledWidth);
break;
case ObjectType.Accessory:
ret = Combos.AccessorySlot("##slot", identifier.EquipSlot, out slot, unscaledWidth);
break;
default: return false;
}
ImUtf8.HoverTooltip("Equip Slot"u8);
if (ret)
identifier = identifier with { EquipSlot = slot };
return ret;
}
public static bool DrawMaterialId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##materialId"u8, "Material ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialId, defaultEntry.MaterialId,
out var newValue, (byte)1, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { MaterialId = newValue };
return true;
}
public static bool DrawMaterialAnimationId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##mAnimId"u8, "Material Animation ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialAnimationId,
defaultEntry.MaterialAnimationId, out var newValue, (byte)0, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { MaterialAnimationId = newValue };
return true;
}
public static bool DrawDecalId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##decalId"u8, "Decal ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.DecalId, defaultEntry.DecalId, out var newValue,
(byte)0, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { DecalId = newValue };
return true;
}
public static bool DrawVfxId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##vfxId"u8, "VFX ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.VfxId, defaultEntry.VfxId, out var newValue, (byte)0,
byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { VfxId = newValue };
return true;
}
public static bool DrawSoundId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##soundId"u8, "Sound ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.SoundId, defaultEntry.SoundId, out var newValue,
(byte)0, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { SoundId = newValue };
return true;
}
public static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry)
{
var changes = false;
for (var i = 0; i < ImcEntry.NumAttributes; ++i)
{
using var id = ImRaii.PushId(i);
var flag = 1 << i;
var value = (entry.AttributeMask & flag) != 0;
var def = (defaultEntry.AttributeMask & flag) != 0;
if (Checkmark("##attribute"u8, "ABCDEFGHIJ"u8.Slice(i, 1), value, def, out var newValue))
{
var newMask = (ushort)(newValue ? entry.AttributeMask | flag : entry.AttributeMask & ~flag);
entry = entry with { AttributeMask = newMask };
changes = true;
}
if (i < ImcEntry.NumAttributes - 1)
ImUtf8.SameLineInner();
}
return changes;
}
/// <summary>
/// A number input for ids with an optional max id of given width.
/// Returns true if newId changed against currentId.
/// </summary>
private static bool IdInput(ReadOnlySpan<byte> label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId,
bool border)
{
int tmp = currentId;
ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale);
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border);
using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border);
if (ImUtf8.InputScalar(label, ref tmp))
tmp = Math.Clamp(tmp, minId, maxId);
newId = (ushort)tmp;
return newId != currentId;
}
/// <summary>
/// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max.
/// Returns true if newValue changed against currentValue.
/// </summary>
private static bool DragInput<T>(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, float width, T currentValue, T defaultValue,
out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber<T>
{
newValue = currentValue;
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
defaultValue != currentValue);
ImGui.SetNextItemWidth(width);
if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed))
newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue;
if (addDefault)
ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}");
else
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
return newValue != currentValue;
}
/// <summary>
/// A checkmark that compares against a default value and shows a tooltip.
/// Returns true if newValue is changed against currentValue.
/// </summary>
private static bool Checkmark(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, bool currentValue, bool defaultValue,
out bool newValue)
{
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
defaultValue != currentValue);
newValue = currentValue;
ImUtf8.Checkbox(label, ref newValue);
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
return newValue != currentValue;
}
}

View file

@ -1,10 +1,11 @@
using System.Reflection.Emit;
using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Text.EndObjects;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Files;
@ -20,17 +21,7 @@ public partial class ModEditWindow
private const string ModelSetIdTooltip =
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
private const string ModelSetIdTooltipShort = "Model Set ID";
private const string EquipSlotTooltip = "Equip Slot";
private const string ModelRaceTooltip = "Model Race";
private const string GenderTooltip = "Gender";
private const string ObjectTypeTooltip = "Object Type";
private const string SecondaryIdTooltip = "Secondary ID";
private const string PrimaryIdTooltipShort = "Primary ID";
private const string VariantIdTooltip = "Variant ID";
private const string EstTypeTooltip = "EST Type";
private const string RacialTribeTooltip = "Racial Tribe";
private const string ScalingTypeTooltip = "Scaling Type";
private void DrawMetaTab()
{
@ -56,7 +47,7 @@ public partial class ModEditWindow
ImGui.SameLine();
SetFromClipboardButton();
ImGui.SameLine();
CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor.Recombine());
CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor);
ImGui.SameLine();
if (ImGui.Button("Write as TexTools Files"))
_metaFileManager.WriteAllTexToolsMeta(Mod!);
@ -65,71 +56,103 @@ public partial class ModEditWindow
if (!child)
return;
DrawEditHeader(_editor.MetaEditor.Eqp, "Equipment Parameter Edits (EQP)###EQP", 5, EqpRow.Draw, EqpRow.DrawNew,
_editor.MetaEditor.OtherData[MetaManipulation.Type.Eqp]);
DrawEditHeader(_editor.MetaEditor.Eqdp, "Racial Model Edits (EQDP)###EQDP", 7, EqdpRow.Draw, EqdpRow.DrawNew,
_editor.MetaEditor.OtherData[MetaManipulation.Type.Eqdp]);
DrawEditHeader(_editor.MetaEditor.Imc, "Variant Edits (IMC)###IMC", 10, ImcRow.Draw, ImcRow.DrawNew,
_editor.MetaEditor.OtherData[MetaManipulation.Type.Imc]);
DrawEditHeader(_editor.MetaEditor.Est, "Extra Skeleton Parameters (EST)###EST", 7, EstRow.Draw, EstRow.DrawNew,
_editor.MetaEditor.OtherData[MetaManipulation.Type.Est]);
DrawEditHeader(_editor.MetaEditor.Gmp, "Visor/Gimmick Edits (GMP)###GMP", 7, GmpRow.Draw, GmpRow.DrawNew,
_editor.MetaEditor.OtherData[MetaManipulation.Type.Gmp]);
DrawEditHeader(_editor.MetaEditor.Rsp, "Racial Scaling Edits (RSP)###RSP", 5, RspRow.Draw, RspRow.DrawNew,
_editor.MetaEditor.OtherData[MetaManipulation.Type.Rsp]);
DrawEditHeader(_editor.MetaEditor.GlobalEqp, "Global Equipment Parameter Edits (Global EQP)###GEQP", 4, GlobalEqpRow.Draw,
GlobalEqpRow.DrawNew, _editor.MetaEditor.OtherData[MetaManipulation.Type.GlobalEqp]);
DrawEditHeader(MetaManipulation.Type.Eqp);
DrawEditHeader(MetaManipulation.Type.Eqdp);
DrawEditHeader(MetaManipulation.Type.Imc);
DrawEditHeader(MetaManipulation.Type.Est);
DrawEditHeader(MetaManipulation.Type.Gmp);
DrawEditHeader(MetaManipulation.Type.Rsp);
DrawEditHeader(MetaManipulation.Type.GlobalEqp);
}
/// <summary> The headers for the different meta changes all have basically the same structure for different types.</summary>
private void DrawEditHeader<T>(IReadOnlyCollection<T> items, string label, int numColumns,
Action<MetaFileManager, T, ModEditor, Vector2> draw, Action<MetaFileManager, ModEditor, Vector2> drawNew,
ModMetaEditor.OtherOptionData otherOptionData)
{
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV;
var oldPos = ImGui.GetCursorPosY();
var header = ImGui.CollapsingHeader($"{items.Count} {label}");
var newPos = ImGui.GetCursorPos();
if (otherOptionData.TotalCount > 0)
private static ReadOnlySpan<byte> Label(MetaManipulation.Type type)
=> type switch
{
var text = $"{otherOptionData.TotalCount} Edits in other Options";
var size = ImGui.CalcTextSize(text).X;
ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionAvail().X - size, oldPos + ImGui.GetStyle().FramePadding.Y));
ImGuiUtil.TextColored(ColorId.RedundantAssignment.Value() | 0xFF000000, text);
if (ImGui.IsItemHovered())
{
using var tt = ImUtf8.Tooltip();
foreach (var name in otherOptionData)
ImUtf8.Text(name);
}
MetaManipulation.Type.Imc => "Variant Edits (IMC)###IMC"u8,
MetaManipulation.Type.Eqdp => "Racial Model Edits (EQDP)###EQDP"u8,
MetaManipulation.Type.Eqp => "Equipment Parameter Edits (EQP)###EQP"u8,
MetaManipulation.Type.Est => "Extra Skeleton Parameters (EST)###EST"u8,
MetaManipulation.Type.Gmp => "Visor/Gimmick Edits (GMP)###GMP"u8,
MetaManipulation.Type.Rsp => "Racial Scaling Edits (RSP)###RSP"u8,
MetaManipulation.Type.GlobalEqp => "Global Equipment Parameter Edits (Global EQP)###GEQP"u8,
_ => "\0"u8,
};
ImGui.SetCursorPos(newPos);
}
private static int ColumnCount(MetaManipulation.Type type)
=> type switch
{
MetaManipulation.Type.Imc => 10,
MetaManipulation.Type.Eqdp => 7,
MetaManipulation.Type.Eqp => 5,
MetaManipulation.Type.Est => 7,
MetaManipulation.Type.Gmp => 7,
MetaManipulation.Type.Rsp => 5,
MetaManipulation.Type.GlobalEqp => 4,
_ => 0,
};
private void DrawEditHeader(MetaManipulation.Type type)
{
var oldPos = ImGui.GetCursorPosY();
var header = ImUtf8.CollapsingHeader($"{_editor.MetaEditor.GetCount(type)} {Label(type)}");
DrawOtherOptionData(type, oldPos, ImGui.GetCursorPos());
if (!header)
return;
using (var table = ImRaii.Table(label, numColumns, flags))
{
if (table)
{
drawNew(_metaFileManager, _editor, _iconSize);
foreach (var (item, index) in items.ToArray().WithIndex())
{
using var id = ImRaii.PushId(index);
draw(_metaFileManager, item, _editor, _iconSize);
}
}
}
DrawTable(type);
}
private IMetaDrawer? Drawer(MetaManipulation.Type type)
=> type switch
{
//MetaManipulation.Type.Imc => expr,
//MetaManipulation.Type.Eqdp => expr,
//MetaManipulation.Type.Eqp => expr,
//MetaManipulation.Type.Est => expr,
//MetaManipulation.Type.Gmp => expr,
//MetaManipulation.Type.Rsp => expr,
//MetaManipulation.Type.GlobalEqp => expr,
_ => null,
};
private void DrawTable(MetaManipulation.Type type)
{
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV;
using var table = ImUtf8.Table(Label(type), ColumnCount(type), flags);
if (!table)
return;
if (Drawer(type) is not { } drawer)
return;
drawer.Draw();
ImGui.NewLine();
}
private void DrawOtherOptionData(MetaManipulation.Type type, float oldPos, Vector2 newPos)
{
var otherOptionData = _editor.MetaEditor.OtherData[type];
if (otherOptionData.TotalCount <= 0)
return;
var text = $"{otherOptionData.TotalCount} Edits in other Options";
var size = ImGui.CalcTextSize(text).X;
ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionAvail().X - size, oldPos + ImGui.GetStyle().FramePadding.Y));
ImGuiUtil.TextColored(ColorId.RedundantAssignment.Value() | 0xFF000000, text);
if (ImGui.IsItemHovered())
{
using var tt = ImUtf8.Tooltip();
foreach (var name in otherOptionData)
ImUtf8.Text(name);
}
ImGui.SetCursorPos(newPos);
}
#if false
private static class EqpRow
{
private static EqpManipulation _new = new(Eqp.DefaultEntry, EquipSlot.Head, 1);
private static EqpIdentifier _newIdentifier = new(1, EquipSlot.Body);
private static float IdWidth
=> 100 * UiHelpers.Scale;
@ -140,8 +163,8 @@ public partial class ModEditWindow
CopyToClipboardButton("Copy all current EQP manipulations to clipboard.", iconSize,
editor.MetaEditor.Eqp.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, _new.SetId);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
editor.MetaEditor.Add(_new.Copy(defaultEntry));
@ -197,7 +220,7 @@ public partial class ModEditWindow
var idx = 0;
foreach (var flag in Eqp.EqpAttributes[meta.Slot])
{
using var id = ImRaii.PushId(idx++);
using var id = ImRaii.PushId(idx++);
var defaultValue = defaultEntry.HasFlag(flag);
var currentValue = meta.Entry.HasFlag(flag);
if (Checkmark("##eqp", flag.ToLocalName(), currentValue, defaultValue, out var value))
@ -209,8 +232,6 @@ public partial class ModEditWindow
ImGui.NewLine();
}
}
private static class EqdpRow
{
private static EqdpManipulation _new = new(EqdpEntry.Invalid, EquipSlot.Head, Gender.Male, ModelRace.Midlander, 1);
@ -224,9 +245,9 @@ public partial class ModEditWindow
CopyToClipboardButton("Copy all current EQDP manipulations to clipboard.", iconSize,
editor.MetaEditor.Eqdp.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var raceCode = Names.CombinedRace(_new.Gender, _new.Race);
var raceCode = Names.CombinedRace(_new.Gender, _new.Race);
var validRaceCode = CharacterUtilityData.EqdpIdx(raceCode, false) >= 0;
var canAdd = validRaceCode && editor.MetaEditor.CanAdd(_new);
var canAdd = validRaceCode && editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." :
validRaceCode ? "This entry is already edited." : "This combination of race and gender can not be used.";
var defaultEntry = validRaceCode
@ -311,7 +332,7 @@ public partial class ModEditWindow
var defaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(),
meta.SetId);
var (defaultBit1, defaultBit2) = defaultEntry.ToBits(meta.Slot);
var (bit1, bit2) = meta.Entry.ToBits(meta.Slot);
var (bit1, bit2) = meta.Entry.ToBits(meta.Slot);
ImGui.TableNextColumn();
if (Checkmark("Material##eqdpCheck1", string.Empty, bit1, defaultBit1, out var newBit1))
editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, newBit1, bit2)));
@ -321,136 +342,7 @@ public partial class ModEditWindow
editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, bit1, newBit2)));
}
}
private static class ImcRow
{
private static ImcIdentifier _newIdentifier = ImcIdentifier.Default;
private static float IdWidth
=> 80 * UiHelpers.Scale;
private static float SmallIdWidth
=> 45 * UiHelpers.Scale;
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
{
ImGui.TableNextColumn();
CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize,
editor.MetaEditor.Imc.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var (defaultEntry, fileExists, _) = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true);
var manip = (MetaManipulation)new ImcManipulation(_newIdentifier, defaultEntry);
var canAdd = fileExists && editor.MetaEditor.CanAdd(manip);
var tt = canAdd ? "Stage this edit." : !fileExists ? "This IMC file does not exist." : "This entry is already edited.";
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
editor.MetaEditor.Add(manip);
// Identifier
ImGui.TableNextColumn();
var change = ImcManipulationDrawer.DrawObjectType(ref _newIdentifier);
ImGui.TableNextColumn();
change |= ImcManipulationDrawer.DrawPrimaryId(ref _newIdentifier);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
ImGui.TableNextColumn();
// Equipment and accessories are slightly different imcs than other types.
if (_newIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier);
else
change |= ImcManipulationDrawer.DrawSecondaryId(ref _newIdentifier);
ImGui.TableNextColumn();
change |= ImcManipulationDrawer.DrawVariant(ref _newIdentifier);
ImGui.TableNextColumn();
if (_newIdentifier.ObjectType is ObjectType.DemiHuman)
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier, 70);
else
ImGui.Dummy(new Vector2(70 * UiHelpers.Scale, 0));
if (change)
defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true).Entry;
// Values
using var disabled = ImRaii.Disabled();
ImGui.TableNextColumn();
ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref defaultEntry, false);
ImGui.SameLine();
ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref defaultEntry, false);
ImGui.TableNextColumn();
ImcManipulationDrawer.DrawDecalId(defaultEntry, ref defaultEntry, false);
ImGui.SameLine();
ImcManipulationDrawer.DrawVfxId(defaultEntry, ref defaultEntry, false);
ImGui.SameLine();
ImcManipulationDrawer.DrawSoundId(defaultEntry, ref defaultEntry, false);
ImGui.TableNextColumn();
ImcManipulationDrawer.DrawAttributes(defaultEntry, ref defaultEntry);
}
public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize)
{
DrawMetaButtons(meta, editor, iconSize);
// Identifier
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
ImGui.TextUnformatted(meta.ObjectType.ToName());
ImGuiUtil.HoverTooltip(ObjectTypeTooltip);
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
ImGui.TextUnformatted(meta.PrimaryId.ToString());
ImGuiUtil.HoverTooltip(PrimaryIdTooltipShort);
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
if (meta.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
{
ImGui.TextUnformatted(meta.EquipSlot.ToName());
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
}
else
{
ImGui.TextUnformatted(meta.SecondaryId.ToString());
ImGuiUtil.HoverTooltip(SecondaryIdTooltip);
}
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
ImGui.TextUnformatted(meta.Variant.ToString());
ImGuiUtil.HoverTooltip(VariantIdTooltip);
ImGui.TableNextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
if (meta.ObjectType is ObjectType.DemiHuman)
{
ImGui.TextUnformatted(meta.EquipSlot.ToName());
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
}
// Values
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
ImGui.TableNextColumn();
var defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(meta.Identifier, true).Entry;
var newEntry = meta.Entry;
var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true);
ImGui.SameLine();
changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true);
ImGui.TableNextColumn();
changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref newEntry, true);
ImGui.SameLine();
changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref newEntry, true);
ImGui.SameLine();
changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref newEntry, true);
ImGui.TableNextColumn();
changes |= ImcManipulationDrawer.DrawAttributes(defaultEntry, ref newEntry);
if (changes)
editor.MetaEditor.Change(meta.Copy(newEntry));
}
}
private static class EstRow
{
private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstType.Body, 1, EstEntry.Zero);
@ -464,8 +356,8 @@ public partial class ModEditWindow
CopyToClipboardButton("Copy all current EST manipulations to clipboard.", iconSize,
editor.MetaEditor.Est.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var defaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
editor.MetaEditor.Add(_new.Copy(defaultEntry));
@ -538,12 +430,11 @@ public partial class ModEditWindow
// Values
var defaultEntry = EstFile.GetDefault(metaFileManager, meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId);
ImGui.TableNextColumn();
if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry.Value, defaultEntry.Value,
if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry.Value, defaultEntry.Value,
out var entry, 0, ushort.MaxValue, 0.05f))
editor.MetaEditor.Change(meta.Copy(new EstEntry((ushort)entry)));
}
}
private static class GmpRow
{
private static GmpManipulation _new = new(GmpEntry.Default, 1);
@ -563,8 +454,8 @@ public partial class ModEditWindow
CopyToClipboardButton("Copy all current GMP manipulations to clipboard.", iconSize,
editor.MetaEditor.Gmp.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, _new.SetId);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
editor.MetaEditor.Add(_new.Copy(defaultEntry));
@ -643,7 +534,6 @@ public partial class ModEditWindow
editor.MetaEditor.Change(meta.Copy(meta.Entry with { UnknownB = (byte)unkB }));
}
}
private static class RspRow
{
private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, RspEntry.One);
@ -657,8 +547,8 @@ public partial class ModEditWindow
CopyToClipboardButton("Copy all current RSP manipulations to clipboard.", iconSize,
editor.MetaEditor.Rsp.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
var defaultEntry = CmpFile.GetDefault(metaFileManager, _new.SubRace, _new.Attribute);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
editor.MetaEditor.Add(_new.Copy(defaultEntry));
@ -700,7 +590,7 @@ public partial class ModEditWindow
ImGui.TableNextColumn();
// Values
var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute).Value;
var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute).Value;
var value = meta.Entry.Value;
ImGui.SetNextItemWidth(FloatWidth);
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
@ -713,12 +603,11 @@ public partial class ModEditWindow
ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}");
}
}
private static class GlobalEqpRow
{
private static GlobalEqpManipulation _new = new()
{
Type = GlobalEqpType.DoNotHideEarrings,
Type = GlobalEqpType.DoNotHideEarrings,
Condition = 1,
};
@ -729,7 +618,7 @@ public partial class ModEditWindow
editor.MetaEditor.GlobalEqp.Select(m => (MetaManipulation)m));
ImGui.TableNextColumn();
var canAdd = editor.MetaEditor.CanAdd(_new);
var tt = canAdd ? "Stage this edit." : "This entry is already manipulated.";
var tt = canAdd ? "Stage this edit." : "This entry is already manipulated.";
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
editor.MetaEditor.Add(_new);
@ -744,7 +633,7 @@ public partial class ModEditWindow
if (ImUtf8.Selectable(type.ToName(), type == _new.Type))
_new = new GlobalEqpManipulation
{
Type = type,
Type = type,
Condition = type.HasCondition() ? _new.Type.HasCondition() ? _new.Condition : 1 : 0,
};
ImUtf8.HoverTooltip(type.ToDescription());
@ -777,6 +666,7 @@ public partial class ModEditWindow
}
}
}
#endif
// A number input for ids with a optional max id of given width.
// Returns true if newId changed against currentId.
@ -824,7 +714,7 @@ public partial class ModEditWindow
return newValue != currentValue;
}
private static void CopyToClipboardButton(string tooltip, Vector2 iconSize, IEnumerable<MetaManipulation> manipulations)
private static void CopyToClipboardButton(string tooltip, Vector2 iconSize, MetaDictionary manipulations)
{
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true))
return;
@ -840,10 +730,9 @@ public partial class ModEditWindow
{
var clipboard = ImGuiUtil.GetClipboardText();
var version = Functions.FromCompressedBase64<MetaManipulation[]>(clipboard, out var manips);
var version = Functions.FromCompressedBase64<MetaDictionary>(clipboard, out var manips);
if (version == MetaManipulation.CurrentVersion && manips != null)
foreach (var manip in manips.Where(m => m.ManipulationType != MetaManipulation.Type.Unknown))
_editor.MetaEditor.Set(manip);
_editor.MetaEditor.UpdateTo(manips);
}
ImGuiUtil.HoverTooltip(
@ -855,13 +744,9 @@ public partial class ModEditWindow
if (ImGui.Button("Set from Clipboard"))
{
var clipboard = ImGuiUtil.GetClipboardText();
var version = Functions.FromCompressedBase64<MetaManipulation[]>(clipboard, out var manips);
var version = Functions.FromCompressedBase64<MetaDictionary>(clipboard, out var manips);
if (version == MetaManipulation.CurrentVersion && manips != null)
{
_editor.MetaEditor.Clear();
foreach (var manip in manips.Where(m => m.ManipulationType != MetaManipulation.Type.Unknown))
_editor.MetaEditor.Set(manip);
}
_editor.MetaEditor.SetTo(manips);
}
ImGuiUtil.HoverTooltip(
@ -870,11 +755,184 @@ public partial class ModEditWindow
private static void DrawMetaButtons(MetaManipulation meta, ModEditor editor, Vector2 iconSize)
{
ImGui.TableNextColumn();
CopyToClipboardButton("Copy this manipulation to clipboard.", iconSize, Array.Empty<MetaManipulation>().Append(meta));
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta manipulation.", false, true))
editor.MetaEditor.Delete(meta);
//ImGui.TableNextColumn();
//CopyToClipboardButton("Copy this manipulation to clipboard.", iconSize, Array.Empty<MetaManipulation>().Append(meta));
//
//ImGui.TableNextColumn();
//if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta manipulation.", false, true))
// editor.MetaEditor.Delete(meta);
}
}
public interface IMetaDrawer
{
public void Draw();
}
public abstract class MetaDrawer<TIdentifier, TEntry>(ModEditor editor, MetaFileManager metaFiles) : IMetaDrawer
where TIdentifier : unmanaged, IMetaIdentifier
where TEntry : unmanaged
{
protected readonly ModEditor Editor = editor;
protected readonly MetaFileManager MetaFiles = metaFiles;
protected TIdentifier Identifier;
protected TEntry Entry;
private bool _initialized;
public void Draw()
{
if (!_initialized)
{
Initialize();
_initialized = true;
}
DrawNew();
foreach (var ((identifier, entry), idx) in Enumerate().WithIndex())
{
using var id = ImUtf8.PushId(idx);
DrawEntry(identifier, entry);
}
}
protected abstract void DrawNew();
protected abstract void Initialize();
protected abstract void DrawEntry(TIdentifier identifier, TEntry entry);
protected abstract IEnumerable<(TIdentifier, TEntry)> Enumerate();
}
#if false
public sealed class GmpMetaDrawer(ModEditor editor) : MetaDrawer<GmpIdentifier, GmpEntry>, IService
{
protected override void Initialize()
{
Identifier = new GmpIdentifier(1, EquipSlot.Body);
UpdateEntry();
}
private void UpdateEntry()
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
protected override void DrawNew()
{ }
protected override void DrawEntry(GmpIdentifier identifier, GmpEntry entry)
{ }
protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate()
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
}
public sealed class EstMetaDrawer(ModEditor editor) : MetaDrawer<EstIdentifier, EstEntry>, IService
{
protected override void Initialize()
{
Identifier = new EqpIdentifier(1, EquipSlot.Body);
UpdateEntry();
}
private void UpdateEntry()
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
protected override void DrawNew()
{ }
protected override void DrawEntry(EstIdentifier identifier, EstEntry entry)
{ }
protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate()
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
}
public sealed class EqdpMetaDrawer(ModEditor editor) : MetaDrawer<EqdpIdentifier, EqdpEntry>, IService
{
protected override void Initialize()
{
Identifier = new EqdpIdentifier(1, EquipSlot.Body);
UpdateEntry();
}
private void UpdateEntry()
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
protected override void DrawNew()
{ }
protected override void DrawEntry(EqdpIdentifier identifier, EqdpEntry entry)
{ }
protected override IEnumerable<(EqdpIdentifier, EqdpEntry)> Enumerate()
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
}
public sealed class EqpMetaDrawer(ModEditor editor, MetaFileManager metaManager) : MetaDrawer<EqpIdentifier, EqpEntry>, IService
{
protected override void Initialize()
{
Identifier = new EqpIdentifier(1, EquipSlot.Body);
UpdateEntry();
}
private void UpdateEntry()
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
protected override void DrawNew()
{ }
protected override void DrawEntry(EqpIdentifier identifier, EqpEntry entry)
{ }
protected override IEnumerable<(EqpIdentifier, EqpEntry)> Enumerate()
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
}
public sealed class RspMetaDrawer(ModEditor editor) : MetaDrawer<RspIdentifier, RspEntry>, IService
{
protected override void Initialize()
{
Identifier = new RspIdentifier(1, EquipSlot.Body);
UpdateEntry();
}
private void UpdateEntry()
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
protected override void DrawNew()
{ }
protected override void DrawEntry(RspIdentifier identifier, RspEntry entry)
{ }
protected override IEnumerable<(RspIdentifier, RspEntry)> Enumerate()
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
}
public sealed class GlobalEqpMetaDrawer(ModEditor editor) : MetaDrawer<GlobalEqpManipulation, byte>, IService
{
protected override void Initialize()
{
Identifier = new EqpIdentifier(1, EquipSlot.Body);
UpdateEntry();
}
private void UpdateEntry()
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
protected override void DrawNew()
{ }
protected override void DrawEntry(GlobalEqpManipulation identifier, byte _)
{ }
protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate()
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
}
#endif

View file

@ -9,6 +9,7 @@ using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Manager.OptionEditor;
using Penumbra.UI.AdvancedWindow.Meta;
using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab.Groups;
@ -79,29 +80,29 @@ public class AddGroupDrawer : IUiService
private void DrawImcInput(float width)
{
var change = ImcManipulationDrawer.DrawObjectType(ref _imcIdentifier, width);
var change = ImcMetaDrawer.DrawObjectType(ref _imcIdentifier, width);
ImUtf8.SameLineInner();
change |= ImcManipulationDrawer.DrawPrimaryId(ref _imcIdentifier, width);
change |= ImcMetaDrawer.DrawPrimaryId(ref _imcIdentifier, width);
if (_imcIdentifier.ObjectType is ObjectType.Weapon or ObjectType.Monster)
{
change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcIdentifier, width);
change |= ImcMetaDrawer.DrawSecondaryId(ref _imcIdentifier, width);
ImUtf8.SameLineInner();
change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, width);
change |= ImcMetaDrawer.DrawVariant(ref _imcIdentifier, width);
}
else if (_imcIdentifier.ObjectType is ObjectType.DemiHuman)
{
var quarterWidth = (width - ImUtf8.ItemInnerSpacing.X / ImUtf8.GlobalScale) / 2;
change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcIdentifier, width);
change |= ImcMetaDrawer.DrawSecondaryId(ref _imcIdentifier, width);
ImUtf8.SameLineInner();
change |= ImcManipulationDrawer.DrawSlot(ref _imcIdentifier, quarterWidth);
change |= ImcMetaDrawer.DrawSlot(ref _imcIdentifier, quarterWidth);
ImUtf8.SameLineInner();
change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, quarterWidth);
change |= ImcMetaDrawer.DrawVariant(ref _imcIdentifier, quarterWidth);
}
else
{
change |= ImcManipulationDrawer.DrawSlot(ref _imcIdentifier, width);
change |= ImcMetaDrawer.DrawSlot(ref _imcIdentifier, width);
ImUtf8.SameLineInner();
change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, width);
change |= ImcMetaDrawer.DrawVariant(ref _imcIdentifier, width);
}
if (change)

View file

@ -7,6 +7,7 @@ using Penumbra.GameData.Structs;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Manager.OptionEditor;
using Penumbra.Mods.SubMods;
using Penumbra.UI.AdvancedWindow.Meta;
namespace Penumbra.UI.ModsTab.Groups;
@ -37,9 +38,9 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
ImGui.SameLine();
using (ImUtf8.Group())
{
changes |= ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref entry, true);
changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref entry, true);
changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref entry, true);
changes |= ImcMetaDrawer.DrawMaterialId(defaultEntry, ref entry, true);
changes |= ImcMetaDrawer.DrawVfxId(defaultEntry, ref entry, true);
changes |= ImcMetaDrawer.DrawDecalId(defaultEntry, ref entry, true);
}
ImGui.SameLine(0, editor.PriorityWidth);
@ -54,8 +55,8 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
using (ImUtf8.Group())
{
changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref entry, true);
changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref entry, true);
changes |= ImcMetaDrawer.DrawMaterialAnimationId(defaultEntry, ref entry, true);
changes |= ImcMetaDrawer.DrawSoundId(defaultEntry, ref entry, true);
var canBeDisabled = group.CanBeDisabled;
if (ImUtf8.Checkbox("##disabled"u8, ref canBeDisabled))
editor.ModManager.OptionEditor.ImcEditor.ChangeCanBeDisabled(group, canBeDisabled);

View file

@ -10,210 +10,5 @@ namespace Penumbra.UI.ModsTab;
public static class ImcManipulationDrawer
{
public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110)
{
var ret = Combos.ImcType("##imcType", identifier.ObjectType, out var type, width);
ImUtf8.HoverTooltip("Object Type"u8);
if (ret)
{
var equipSlot = type switch
{
ObjectType.Equipment => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head,
ObjectType.DemiHuman => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head,
ObjectType.Accessory => identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears,
_ => EquipSlot.Unknown,
};
identifier = identifier with
{
ObjectType = type,
EquipSlot = equipSlot,
SecondaryId = identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId,
};
}
return ret;
}
public static bool DrawPrimaryId(ref ImcIdentifier identifier, float unscaledWidth = 80)
{
var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, identifier.PrimaryId.Id, out var newId, 0, ushort.MaxValue,
identifier.PrimaryId.Id <= 1);
ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8
+ "This should generally not be left <= 1 unless you explicitly want that."u8);
if (ret)
identifier = identifier with { PrimaryId = newId };
return ret;
}
public static bool DrawSecondaryId(ref ImcIdentifier identifier, float unscaledWidth = 100)
{
var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, identifier.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false);
ImUtf8.HoverTooltip("Secondary ID"u8);
if (ret)
identifier = identifier with { SecondaryId = newId };
return ret;
}
public static bool DrawVariant(ref ImcIdentifier identifier, float unscaledWidth = 45)
{
var ret = IdInput("##imcVariant"u8, unscaledWidth, identifier.Variant.Id, out var newId, 0, byte.MaxValue, false);
ImUtf8.HoverTooltip("Variant ID"u8);
if (ret)
identifier = identifier with { Variant = (byte)newId };
return ret;
}
public static bool DrawSlot(ref ImcIdentifier identifier, float unscaledWidth = 100)
{
bool ret;
EquipSlot slot;
switch (identifier.ObjectType)
{
case ObjectType.Equipment:
case ObjectType.DemiHuman:
ret = Combos.EqpEquipSlot("##slot", identifier.EquipSlot, out slot, unscaledWidth);
break;
case ObjectType.Accessory:
ret = Combos.AccessorySlot("##slot", identifier.EquipSlot, out slot, unscaledWidth);
break;
default: return false;
}
ImUtf8.HoverTooltip("Equip Slot"u8);
if (ret)
identifier = identifier with { EquipSlot = slot };
return ret;
}
public static bool DrawMaterialId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##materialId"u8, "Material ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialId, defaultEntry.MaterialId,
out var newValue, (byte)1, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { MaterialId = newValue };
return true;
}
public static bool DrawMaterialAnimationId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##mAnimId"u8, "Material Animation ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialAnimationId,
defaultEntry.MaterialAnimationId, out var newValue, (byte)0, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { MaterialAnimationId = newValue };
return true;
}
public static bool DrawDecalId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##decalId"u8, "Decal ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.DecalId, defaultEntry.DecalId, out var newValue,
(byte)0, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { DecalId = newValue };
return true;
}
public static bool DrawVfxId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##vfxId"u8, "VFX ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.VfxId, defaultEntry.VfxId, out var newValue, (byte)0,
byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { VfxId = newValue };
return true;
}
public static bool DrawSoundId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45)
{
if (!DragInput("##soundId"u8, "Sound ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.SoundId, defaultEntry.SoundId, out var newValue,
(byte)0, byte.MaxValue, 0.01f, addDefault))
return false;
entry = entry with { SoundId = newValue };
return true;
}
public static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry)
{
var changes = false;
for (var i = 0; i < ImcEntry.NumAttributes; ++i)
{
using var id = ImRaii.PushId(i);
var flag = 1 << i;
var value = (entry.AttributeMask & flag) != 0;
var def = (defaultEntry.AttributeMask & flag) != 0;
if (Checkmark("##attribute"u8, "ABCDEFGHIJ"u8.Slice(i, 1), value, def, out var newValue))
{
var newMask = (ushort)(newValue ? entry.AttributeMask | flag : entry.AttributeMask & ~flag);
entry = entry with { AttributeMask = newMask };
changes = true;
}
if (i < ImcEntry.NumAttributes - 1)
ImGui.SameLine();
}
return changes;
}
/// <summary>
/// A number input for ids with an optional max id of given width.
/// Returns true if newId changed against currentId.
/// </summary>
private static bool IdInput(ReadOnlySpan<byte> label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId,
bool border)
{
int tmp = currentId;
ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale);
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border);
using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border);
if (ImUtf8.InputScalar(label, ref tmp))
tmp = Math.Clamp(tmp, minId, maxId);
newId = (ushort)tmp;
return newId != currentId;
}
/// <summary>
/// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max.
/// Returns true if newValue changed against currentValue.
/// </summary>
private static bool DragInput<T>(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, float width, T currentValue, T defaultValue,
out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber<T>
{
newValue = currentValue;
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
defaultValue != currentValue);
ImGui.SetNextItemWidth(width);
if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed))
newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue;
if (addDefault)
ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}");
else
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
return newValue != currentValue;
}
/// <summary>
/// A checkmark that compares against a default value and shows a tooltip.
/// Returns true if newValue is changed against currentValue.
/// </summary>
private static bool Checkmark(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, bool currentValue, bool defaultValue,
out bool newValue)
{
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
defaultValue != currentValue);
newValue = currentValue;
ImUtf8.Checkbox(label, ref newValue);
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
return newValue != currentValue;
}
}

View file

@ -45,6 +45,18 @@ public static class DictionaryExtensions
lhs.Add(key, value);
}
/// <summary> Set all entries in the right-hand dictionary to the same values in the left-hand dictionary, ensuring capacity beforehand. </summary>
public static void UpdateTo<TKey, TValue>(this Dictionary<TKey, TValue> lhs, IReadOnlyDictionary<TKey, TValue> rhs)
where TKey : notnull
{
if (ReferenceEquals(lhs, rhs))
return;
lhs.EnsureCapacity(rhs.Count);
foreach (var (key, value) in rhs)
lhs[key] = value;
}
/// <summary> Set one set to the other, deleting previous entries and ensuring capacity beforehand. </summary>
public static void SetTo<T>(this HashSet<T> lhs, IReadOnlySet<T> rhs)
{