Add some new things, rework Revert-handling.

This commit is contained in:
Ottermandias 2024-02-28 15:48:24 +01:00
parent f2951ca800
commit 1cf0e2f70e
21 changed files with 477 additions and 181 deletions

View file

@ -18,18 +18,18 @@ public enum ApplicationType : byte
public static class ApplicationTypeExtensions
{
public static readonly IReadOnlyList<(ApplicationType, string)> Types = new[]
{
public static readonly IReadOnlyList<(ApplicationType, string)> Types =
[
(ApplicationType.Customizations,
"Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."),
(ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."),
(ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."),
(ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."),
(ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
};
];
public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat(
this ApplicationType type, DesignBase? design)
this ApplicationType type, IDesignStandIn designStandIn)
{
var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0)
@ -42,7 +42,7 @@ public static class ApplicationTypeExtensions
| (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0)
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0);
if (design == null)
if (designStandIn is not DesignBase design)
return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag);
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,

View file

@ -1,7 +1,6 @@
using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -12,20 +11,11 @@ public class AutoDesign
{
public const string RevertName = "Revert";
public Design? Design;
public IDesignStandIn Design = new RevertDesign();
public JobGroup Jobs;
public ApplicationType Type;
public short GearsetIndex = -1;
public string Name(bool incognito)
=> Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text;
public ref readonly DesignData GetDesignData(ActorState state)
=> ref Design == null ? ref state.BaseData : ref Design.DesignData;
public bool Revert
=> Design == null;
public AutoDesign Clone()
=> new()
{
@ -50,12 +40,16 @@ public class AutoDesign
}
public JObject Serialize()
=> new()
{
var ret = new JObject
{
["Design"] = Design?.Identifier.ToString(),
["Design"] = Design.SerializeName(),
["Type"] = (uint)Type,
["Conditions"] = CreateConditionObject(),
};
Design.AddData(ret);
return ret;
}
private JObject CreateConditionObject()
{

View file

@ -263,7 +263,7 @@ public sealed class AutoDesignApplier : IDisposable
return;
var mergedDesign = _designMerger.Merge(
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]),
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type))),
state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods);
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, set.BaseState is AutoDesignSet.Base.Game));
}

View file

@ -21,11 +21,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
private readonly SaveService _saveService;
private readonly JobService _jobs;
private readonly DesignManager _designs;
private readonly ActorManager _actors;
private readonly AutomationChanged _event;
private readonly DesignChanged _designEvent;
private readonly JobService _jobs;
private readonly DesignManager _designs;
private readonly ActorManager _actors;
private readonly AutomationChanged _event;
private readonly DesignChanged _designEvent;
private readonly RandomDesignGenerator _randomDesigns;
private readonly List<AutoDesignSet> _data = [];
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = [];
@ -34,14 +35,15 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
=> _enabled;
public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event,
FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent)
FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent, RandomDesignGenerator randomDesigns)
{
_jobs = jobs;
_actors = actors;
_saveService = saveService;
_designs = designs;
_event = @event;
_designEvent = designEvent;
_jobs = jobs;
_actors = actors;
_saveService = saveService;
_designs = designs;
_event = @event;
_designEvent = designEvent;
_randomDesigns = randomDesigns;
_designEvent.Subscribe(OnDesignChange, DesignChanged.Priority.AutoDesignManager);
Load();
migrator.ConsumeMigratedData(_actors, fileSystem, this);
@ -227,7 +229,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase));
}
public void AddDesign(AutoDesignSet set, Design? design)
public void AddDesign(AutoDesignSet set, IDesignStandIn design)
{
var newDesign = new AutoDesign()
{
@ -238,7 +240,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
set.Designs.Add(newDesign);
Save();
Glamourer.Log.Debug(
$"Added new associated design {design?.Identifier.ToString() ?? "Reverter"} as design {set.Designs.Count} to design set.");
$"Added new associated design {design.ResolveName(true)} as design {set.Designs.Count} to design set.");
_event.Invoke(AutomationChanged.Type.AddedDesign, set, set.Designs.Count - 1);
}
@ -278,20 +280,20 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_event.Invoke(AutomationChanged.Type.MovedDesign, set, (from, to));
}
public void ChangeDesign(AutoDesignSet set, int which, Design? newDesign)
public void ChangeDesign(AutoDesignSet set, int which, IDesignStandIn newDesign)
{
if (which >= set.Designs.Count || which < 0)
return;
var design = set.Designs[which];
if (design.Design?.Identifier == newDesign?.Identifier)
if (design.Design.Equals(newDesign))
return;
var old = design.Design;
design.Design = newDesign;
Save();
Glamourer.Log.Debug(
$"Changed linked design from {old?.Identifier.ToString() ?? "Reverter"} to {newDesign?.Identifier.ToString() ?? "Reverter"} for associated design {which + 1} in design set.");
$"Changed linked design from {old.ResolveName(true)} to {newDesign.ResolveName(true)} for associated design {which + 1} in design set.");
_event.Invoke(AutomationChanged.Type.ChangedDesign, set, (which, old, newDesign));
}
@ -450,8 +452,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
continue;
}
var design = ToDesignObject(set.Name, j);
if (design != null)
if (ToDesignObject(set.Name, j) is { } design)
set.Designs.Add(design);
}
}
@ -459,10 +460,18 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
private AutoDesign? ToDesignObject(string setName, JObject jObj)
{
var designIdentifier = jObj["Design"]?.ToObject<string?>();
Design? design = null;
// designIdentifier == null means Revert-Design.
if (designIdentifier != null)
var designIdentifier = jObj["Design"]?.ToObject<string?>();
IDesignStandIn? design;
// designIdentifier == null means Revert-Design for backwards compatibility
if (designIdentifier is null or RevertDesign.SerializedName)
{
design = new RevertDesign();
}
else if (designIdentifier is RandomDesign.SerializedName)
{
design = new RandomDesign(_randomDesigns);
}
else
{
if (designIdentifier.Length == 0)
{
@ -480,14 +489,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
return null;
}
if (!_designs.Designs.TryGetValue(guid, out design))
if (!_designs.Designs.TryGetValue(guid, out var d))
{
Glamourer.Messager.NotificationMessage(
$"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.",
NotificationType.Warning);
return null;
}
design = d;
}
design.ParseData(jObj);
// ApplicationType is a migration from an older property name.
var applicationType = (ApplicationType)(jObj["Type"]?.ToObject<uint>() ?? jObj["ApplicationType"]?.ToObject<uint>() ?? 0);

View file

@ -29,7 +29,7 @@ public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List<Auto
}
public AutoDesignSet(string name, params ActorIdentifier[] identifiers)
: this(name, identifiers, new List<AutoDesign>())
: this(name, identifiers, [])
{ }
public enum Base : byte

View file

@ -1,15 +1,17 @@
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Automation;
using Glamourer.Designs.Links;
using Glamourer.Interop.Material;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
namespace Glamourer.Designs;
public sealed class Design : DesignBase, ISavable
public sealed class Design : DesignBase, ISavable, IDesignStandIn
{
#region Data
@ -48,8 +50,36 @@ public sealed class Design : DesignBase, ISavable
public string Incognito
=> Identifier.ToString()[..8];
public IEnumerable<(DesignBase? Design, ApplicationType Flags)> AllLinks
=> LinkContainer.GetAllLinks(this).Select(t => ((DesignBase?)t.Link.Link, t.Link.Type));
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks
=> LinkContainer.GetAllLinks(this).Select(t => ((IDesignStandIn)t.Link.Link, t.Link.Type));
#endregion
#region IDesignStandIn
public string ResolveName(bool incognito)
=> incognito ? Incognito : Name.Text;
public string SerializeName()
=> Identifier.ToString();
public ref readonly DesignData GetDesignData(in DesignData baseData)
=> ref GetDesignDataRef();
public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData()
=> Materials;
public bool Equals(IDesignStandIn? other)
=> other is Design d && d.Identifier == Identifier;
public StateSource AssociatedSource()
=> StateSource.Manual;
public void AddData(JObject _)
{ }
public void ParseData(JObject _)
{ }
#endregion

View file

@ -149,8 +149,11 @@ public class DesignColors : ISavable, IReadOnlyDictionary<string, uint>
Load();
}
public uint GetColor(Design design)
public uint GetColor(Design? design)
{
if (design == null)
return ColorId.NormalDesign.Value();
if (design.Color.Length == 0)
return AutoColor(design);

View file

@ -0,0 +1,23 @@
using Glamourer.Automation;
using Glamourer.Interop.Material;
using Glamourer.State;
using Newtonsoft.Json.Linq;
namespace Glamourer.Designs;
public interface IDesignStandIn : IEquatable<IDesignStandIn>
{
public string ResolveName(bool incognito);
public ref readonly DesignData GetDesignData(in DesignData baseRef);
public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData();
public string SerializeName();
public StateSource AssociatedSource();
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks { get; }
public void AddData(JObject jObj);
public void ParseData(JObject jObj);
}

View file

@ -17,13 +17,15 @@ public class DesignMerger(
ItemUnlockManager _itemUnlocks,
CustomizeUnlockManager _customizeUnlocks) : IService
{
public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, bool modAssociations)
=> Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations);
public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership,
bool modAssociations)
=> Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations);
public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership,
public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType)> designs, in CustomizeArray currentCustomize, in DesignData baseRef,
bool respectOwnership,
bool modAssociations)
{
var ret = new MergedDesign(designManager);
var ret = new MergedDesign(designManager);
ret.Design.SetCustomize(_customize, currentCustomize);
CustomizeFlag fixFlags = 0;
respectOwnership &= _config.UnlockedItemMode;
@ -32,8 +34,8 @@ public class DesignMerger(
if (type is 0)
continue;
ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef();
var source = design == null ? StateSource.Game : StateSource.Manual;
ref readonly var data = ref design.GetDesignData(baseRef);
var source = design.AssociatedSource();
if (!data.IsHuman)
continue;
@ -56,10 +58,11 @@ public class DesignMerger(
}
private static void ReduceMaterials(DesignBase? design, MergedDesign ret)
private static void ReduceMaterials(IDesignStandIn designStandIn, MergedDesign ret)
{
if (design == null)
if (designStandIn is not DesignBase design)
return;
var materials = ret.Design.GetMaterialDataRef();
foreach (var (key, value) in design.Materials.Where(p => p.Item2.Enabled))
materials.TryAddValue(MaterialValueIndex.FromKey(key), value);
@ -243,7 +246,7 @@ public class DesignMerger(
ret.Sources[CustomizeIndex.Face] = source;
}
var set = _customize.Manager.GetSet(customize.Clan, customize.Gender);
var set = _customize.Manager.GetSet(customize.Clan, customize.Gender);
var face = customize.Face;
foreach (var index in Enum.GetValues<CustomizeIndex>())
{

View file

@ -0,0 +1,69 @@
using Glamourer.Automation;
using Glamourer.Interop.Material;
using Glamourer.State;
using Newtonsoft.Json.Linq;
namespace Glamourer.Designs;
public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
{
public const string SerializedName = "//Random";
public const string ResolvedName = "Random";
private Design? _currentDesign;
public string Restrictions { get; internal set; } = string.Empty;
public string ResolveName(bool _)
=> ResolvedName;
public ref readonly DesignData GetDesignData(in DesignData baseRef)
{
_currentDesign ??= rng.Design(Restrictions);
if (_currentDesign == null)
return ref baseRef;
return ref _currentDesign.GetDesignDataRef();
}
public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData()
{
_currentDesign ??= rng.Design(Restrictions);
if (_currentDesign == null)
return [];
return _currentDesign.Materials;
}
public string SerializeName()
=> SerializedName;
public bool Equals(IDesignStandIn? other)
=> other is RandomDesign r && string.Equals(r.Restrictions, Restrictions, StringComparison.OrdinalIgnoreCase);
public StateSource AssociatedSource()
=> StateSource.Manual;
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks
{
get
{
_currentDesign = rng.Design(Restrictions);
if (_currentDesign == null)
yield break;
foreach (var (link, type) in _currentDesign.AllLinks)
yield return (link, type);
}
}
public void AddData(JObject jObj)
{
jObj["Restrictions"] = Restrictions;
}
public void ParseData(JObject jObj)
{
var restrictions = jObj["Restrictions"]?.ToObject<string>() ?? string.Empty;
Restrictions = restrictions;
}
}

View file

@ -0,0 +1,91 @@
using OtterGui;
using OtterGui.Services;
using System;
namespace Glamourer.Designs;
public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService
{
private readonly Random _rng = new();
public Design? Design(IReadOnlyList<Design> localDesigns)
{
if (localDesigns.Count == 0)
return null;
var idx = _rng.Next(0, localDesigns.Count - 1);
Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}.");
return localDesigns[idx];
}
public Design? Design()
=> Design(designs);
public Design? Design(string restrictions)
{
if (restrictions.Length == 0)
return Design(designs);
List<Func<string, string, string, bool>> predicates = [];
switch (restrictions[0])
{
case '{':
var end = restrictions.IndexOf('}');
if (end == -1)
throw new ArgumentException($"The restriction group '{restrictions}' is not properly terminated.");
restrictions = restrictions[1..end];
var split = restrictions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var item in split.Distinct())
predicates.Add(item[0] == '/' ? CreatePredicateSlash(item) : CreatePredicate(item));
break;
case '/':
predicates.Add(CreatePredicateSlash(restrictions));
break;
default:
predicates.Add(CreatePredicate(restrictions));
break;
}
if (predicates.Count == 1)
{
var p = predicates[0];
return Design(designs.Select(Transform).Where(t => p(t.NameLower, t.Identifier, t.PathLower)).Select(t => t.Design).ToList());
}
return Design(designs.Select(Transform).Where(t => predicates.Any(p => p(t.NameLower, t.Identifier, t.PathLower))).Select(t => t.Design)
.ToList());
(Design Design, string NameLower, string Identifier, string PathLower) Transform(Design design)
{
var name = design.Name.Lower;
var identifier = design.Identifier.ToString();
var path = fileSystem.FindLeaf(design, out var leaf) ? leaf.FullName().ToLowerInvariant() : string.Empty;
return (design, name, identifier, path);
}
Func<string, string, string, bool> CreatePredicate(string input)
{
var value = input.ToLowerInvariant();
return (string nameLower, string identifier, string pathLower) =>
{
if (nameLower.Contains(value))
return true;
if (identifier.Contains(value))
return true;
if (pathLower.Contains(value))
return true;
return false;
};
}
Func<string, string, string, bool> CreatePredicateSlash(string input)
{
var value = input[1..].ToLowerInvariant();
return (string nameLower, string identifier, string pathLower)
=> pathLower.StartsWith(value);
}
}
}

View file

@ -0,0 +1,41 @@
using Glamourer.Automation;
using Glamourer.Interop.Material;
using Glamourer.State;
using Newtonsoft.Json.Linq;
namespace Glamourer.Designs;
public class RevertDesign : IDesignStandIn
{
public const string SerializedName = "//Revert";
public const string ResolvedName = "Revert";
public string ResolveName(bool _)
=> ResolvedName;
public ref readonly DesignData GetDesignData(in DesignData baseRef)
=> ref baseRef;
public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData()
=> [];
public string SerializeName()
=> SerializedName;
public bool Equals(IDesignStandIn? other)
=> other is RevertDesign;
public StateSource AssociatedSource()
=> StateSource.Game;
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks
{
get { yield return (this, ApplicationType.All); }
}
public void AddData(JObject jObj)
{ }
public void ParseData(JObject jObj)
{ }
}

View file

@ -122,7 +122,7 @@ public sealed class DesignChanged()
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnDesignChange"/>
DesignFileSystemSelector = -1,
/// <seealso cref="RevertDesignCombo.OnDesignChange"/>
/// <seealso cref="SpecialDesignCombo.OnDesignChange"/>
DesignCombo = -2,
}
}

View file

@ -3,26 +3,24 @@ using Dalamud.Interface.Utility.Raii;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Log;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui;
public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>, IDisposable
public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, string>>, IDisposable
{
private readonly EphemeralConfig _config;
private readonly DesignChanged _designChanged;
private readonly DesignColors _designColors;
protected readonly TabSelected TabSelected;
protected float InnerWidth;
private Design? _currentDesign;
private IDesignStandIn? _currentDesign;
protected DesignComboBase(Func<IReadOnlyList<Tuple<Design, string>>> generator, Logger log, DesignChanged designChanged,
protected DesignComboBase(Func<IReadOnlyList<Tuple<IDesignStandIn, string>>> generator, Logger log, DesignChanged designChanged,
TabSelected tabSelected, EphemeralConfig config, DesignColors designColors)
: base(generator, MouseWheelType.Control, log)
{
@ -46,28 +44,34 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
{
var (design, path) = Items[globalIdx];
bool ret;
using (var color = ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(design)))
if (design is Design realDesign)
{
using var color = ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(realDesign));
ret = base.DrawSelectable(globalIdx, selected);
if (path.Length > 0 && realDesign.Name != path)
{
var start = ImGui.GetItemRectMin();
var pos = start.X + ImGui.CalcTextSize(realDesign.Name).X;
var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
var remainingSpace = maxSize - pos;
var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X;
var offset = remainingSpace - requiredSize;
if (ImGui.GetScrollMaxY() == 0)
offset -= ImGui.GetStyle().ItemInnerSpacing.X;
if (offset < ImGui.GetStyle().ItemSpacing.X)
ImGuiUtil.HoverTooltip(path);
else
ImGui.GetWindowDrawList().AddText(start with { X = pos + offset },
ImGui.GetColorU32(ImGuiCol.TextDisabled), path);
}
}
else
{
ret = base.DrawSelectable(globalIdx, selected);
}
if (path.Length > 0 && design.Name != path)
{
var start = ImGui.GetItemRectMin();
var pos = start.X + ImGui.CalcTextSize(design.Name).X;
var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
var remainingSpace = maxSize - pos;
var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X;
var offset = remainingSpace - requiredSize;
if (ImGui.GetScrollMaxY() == 0)
offset -= ImGui.GetStyle().ItemInnerSpacing.X;
if (offset < ImGui.GetStyle().ItemSpacing.X)
ImGuiUtil.HoverTooltip(path);
else
ImGui.GetWindowDrawList().AddText(start with { X = pos + offset },
ImGui.GetColorU32(ImGuiCol.TextDisabled), path);
}
return ret;
}
@ -79,22 +83,22 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
return CurrentSelectionIdx;
}
protected bool Draw(Design? currentDesign, string? label, float width)
protected bool Draw(IDesignStandIn? currentDesign, string? label, float width)
{
_currentDesign = currentDesign;
InnerWidth = 400 * ImGuiHelpers.GlobalScale;
var name = label ?? "Select Design Here...";
bool ret;
using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(currentDesign)) : null)
using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(currentDesign as Design)) : null)
{
ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing())
&& CurrentSelection != null;
}
if (currentDesign != null)
if (currentDesign is Design design)
{
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl)
TabSelected.Invoke(MainWindow.TabType.Designs, currentDesign);
TabSelected.Invoke(MainWindow.TabType.Designs, design);
ImGuiUtil.HoverTooltip("Control + Right-Click to move to design.");
}
@ -102,8 +106,8 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
return ret;
}
protected override string ToString(Tuple<Design, string> obj)
=> obj.Item1.Name.Text;
protected override string ToString(Tuple<IDesignStandIn, string> obj)
=> obj.Item1.ResolveName(Incognito);
protected override float GetFilterWidth()
=> InnerWidth - 2 * ImGui.GetStyle().FramePadding.X;
@ -111,7 +115,7 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
protected override bool IsVisible(int globalIndex, LowerString filter)
{
var (design, path) = Items[globalIndex];
return filter.IsContained(path) || design.Name.Lower.Contains(filter.Lower);
return filter.IsContained(path) || filter.IsContained(design.ResolveName(false));
}
private void OnDesignChange(DesignChanged.Type type, Design design, object? data = null)
@ -151,7 +155,7 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
public abstract class DesignCombo : DesignComboBase
{
protected DesignCombo(Logger log, DesignChanged designChanged, TabSelected tabSelected,
EphemeralConfig config, DesignColors designColors, Func<IReadOnlyList<Tuple<Design, string>>> generator)
EphemeralConfig config, DesignColors designColors, Func<IReadOnlyList<Tuple<IDesignStandIn, string>>> generator)
: base(generator, log, designChanged, tabSelected, config, designColors)
{
if (Items.Count == 0)
@ -162,11 +166,11 @@ public abstract class DesignCombo : DesignComboBase
base.Cleanup();
}
public Design? Design
public IDesignStandIn? Design
=> CurrentSelection?.Item1;
public void Draw(float width)
=> Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width);
=> Draw(Design, Design?.ResolveName(Incognito) ?? string.Empty, width);
}
public sealed class QuickDesignCombo : DesignCombo
@ -182,7 +186,7 @@ public sealed class QuickDesignCombo : DesignCombo
[
.. designs.Designs
.Where(d => d.QuickDesign)
.Select(d => new Tuple<Design, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.OrderBy(d => d.Item2),
])
=> AllowMouseWheel = MouseWheelType.Unmodified;
@ -199,51 +203,35 @@ public sealed class LinkDesignCombo(
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
[
.. designs.Designs
.Select(d => new Tuple<Design, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.OrderBy(d => d.Item2),
]);
public sealed class RevertDesignCombo : DesignComboBase
public sealed class SpecialDesignCombo(
DesignManager designs,
DesignFileSystem fileSystem,
TabSelected tabSelected,
DesignColors designColors,
Logger log,
DesignChanged designChanged,
AutoDesignManager autoDesignManager,
EphemeralConfig config,
RandomDesignGenerator rng)
: DesignComboBase(() => designs.Designs
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.OrderBy(d => d.Item2)
.Prepend(new Tuple<IDesignStandIn, string>(new RandomDesign(rng), string.Empty))
.Prepend(new Tuple<IDesignStandIn, string>(new RevertDesign(), string.Empty))
.ToList(), log, designChanged, tabSelected, config, designColors)
{
public const int RevertDesignIndex = -1228;
public readonly Design RevertDesign;
private readonly AutoDesignManager _autoDesignManager;
public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors,
ItemManager items, CustomizeService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager,
EphemeralConfig config)
: this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager,
config)
{ }
private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors,
Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, EphemeralConfig config)
: base(() => designs.Designs
.Select(d => new Tuple<Design, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.OrderBy(d => d.Item2)
.Prepend(new Tuple<Design, string>(revertDesign, string.Empty))
.ToList(), log, designChanged, tabSelected, config, designColors)
{
RevertDesign = revertDesign;
_autoDesignManager = autoDesignManager;
}
public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex)
{
if (!Draw(design?.Design, design?.Name(Incognito), ImGui.GetContentRegionAvail().X))
if (!Draw(design?.Design, design?.Design.ResolveName(Incognito), ImGui.GetContentRegionAvail().X))
return;
if (autoDesignIndex >= 0)
_autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1);
autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1);
else
_autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1);
autoDesignManager.AddDesign(set, CurrentSelection!.Item1);
}
private static Design CreateRevertDesign(CustomizeService customize, ItemManager items)
=> new(customize, items)
{
Index = RevertDesignIndex,
Name = AutoDesign.RevertName,
ApplyCustomize = CustomizeFlagExtensions.AllRelevant,
};
}

View file

@ -123,7 +123,7 @@ public sealed class DesignQuickBar : Window, IDisposable
private void DrawApplyButton(Vector2 size)
{
var design = _designCombo.Design;
var design = _designCombo.Design as Design;
var available = 0;
var tooltip = string.Empty;
if (design == null)
@ -135,7 +135,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (_playerIdentifier.IsValid && _playerData.Valid)
{
available |= 1;
tooltip = $"Left-Click: Apply {(_config.Ephemeral.IncognitoMode ? design.Incognito : design.Name)} to yourself.";
tooltip = $"Left-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to yourself.";
}
if (_targetIdentifier.IsValid && _targetData.Valid)
@ -143,7 +143,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (available != 0)
tooltip += '\n';
available |= 2;
tooltip += $"Right-Click: Apply {(_config.Ephemeral.IncognitoMode ? design.Incognito : design.Name)} to {_targetIdentifier}.";
tooltip += $"Right-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to {_targetIdentifier}.";
}
if (available == 0)
@ -157,7 +157,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (state == null && !_stateManager.GetOrCreate(id, data.Objects[0], out state))
{
Glamourer.Messager.NotificationMessage($"Could not apply {design!.Incognito} to {id.Incognito(null)}: Failed to create state.");
Glamourer.Messager.NotificationMessage($"Could not apply {design!.ResolveName(true)} to {id.Incognito(null)}: Failed to create state.");
return;
}

View file

@ -1,6 +1,7 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Unlocks;
@ -20,7 +21,7 @@ public class SetPanel(
AutoDesignManager _manager,
JobService _jobs,
ItemUnlockManager _itemUnlocks,
RevertDesignCombo _designCombo,
SpecialDesignCombo _designCombo,
CustomizeUnlockManager _customizeUnlocks,
CustomizeService _customizations,
IdentifierDrawer _identifierDrawer,
@ -258,21 +259,22 @@ public class SetPanel(
private void DrawWarnings(AutoDesign design)
{
if (design.Revert)
if (design.Design is not DesignBase)
return;
var size = new Vector2(ImGui.GetFrameHeight());
size.X += ImGuiHelpers.GlobalScale;
var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat();
var sb = new StringBuilder();
var sb = new StringBuilder();
var designData = design.Design.GetDesignData(default);
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
{
var flag = slot.ToFlag();
if (!equipFlags.HasFlag(flag))
continue;
var item = design.Design!.DesignData.Item(slot);
var item = designData.Item(slot);
if (!_itemUnlocks.IsUnlocked(item.Id, out _))
sb.AppendLine($"{item.Name} in {slot.ToName()} slot is not unlocked. Consider obtaining it via gameplay means!");
}
@ -286,8 +288,8 @@ public class SetPanel(
sb.Clear();
var sb2 = new StringBuilder();
var customize = design.Design!.DesignData.Customize;
if (!design.Design.DesignData.IsHuman)
var customize = designData.Customize;
if (!designData.IsHuman)
sb.AppendLine("The base model id can not be changed automatically to something non-human.");
var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender);

View file

@ -41,7 +41,7 @@ public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDr
foreach (var (design, designIdx) in set.Designs.WithIndex())
{
ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})");
ImGuiUtil.DrawTableColumn($"{design.Design.ResolveName(false)} ({designIdx})");
ImGuiUtil.DrawTableColumn($"{design.Type} {design.Jobs.Name}");
}
}

View file

@ -152,32 +152,33 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe
ImGui.TableNextColumn();
string ttBefore, ttAfter;
bool canAddBefore, canAddAfter;
if (_combo.Design == null)
var design = _combo.Design as Design;
if (design == null)
{
ttAfter = ttBefore = "Select a design first.";
canAddBefore = canAddAfter = false;
}
else
{
canAddBefore = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.Before, out var error);
canAddBefore = LinkContainer.CanAddLink(_selector.Selected!, design, LinkOrder.Before, out var error);
ttBefore = canAddBefore
? $"Add a link at the top of the list to {_combo.Design.Name}."
: $"Can not add a link to {_combo.Design.Name}:\n{error}";
canAddAfter = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.After, out error);
? $"Add a link at the top of the list to {design.Name}."
: $"Can not add a link to {design.Name}:\n{error}";
canAddAfter = LinkContainer.CanAddLink(_selector.Selected!, design, LinkOrder.After, out error);
ttAfter = canAddAfter
? $"Add a link at the bottom of the list to {_combo.Design.Name}."
: $"Can not add a link to {_combo.Design.Name}:\n{error}";
? $"Add a link at the bottom of the list to {design.Name}."
: $"Can not add a link to {design.Name}:\n{error}";
}
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleUp.ToIconString(), buttonSize, ttBefore, !canAddBefore, true))
{
_linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.Before);
_linkManager.AddDesignLink(_selector.Selected!, design!, LinkOrder.Before);
_linkManager.MoveDesignLink(_selector.Selected!, _selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before);
}
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleDown.ToIconString(), buttonSize, ttAfter, !canAddAfter, true))
_linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.After);
_linkManager.AddDesignLink(_selector.Selected!, design!, LinkOrder.After);
}
private void DrawDragDrop(Design design, LinkOrder order, int index)

View file

@ -19,28 +19,30 @@ namespace Glamourer.Services;
public class CommandService : IDisposable
{
private const string RandomString = "random";
private const string MainCommandString = "/glamourer";
private const string ApplyCommandString = "/glamour";
private readonly ICommandManager _commands;
private readonly MainWindow _mainWindow;
private readonly IChatGui _chat;
private readonly ActorManager _actors;
private readonly ObjectManager _objects;
private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly AutoDesignManager _autoDesignManager;
private readonly DesignManager _designManager;
private readonly DesignConverter _converter;
private readonly DesignFileSystem _designFileSystem;
private readonly Configuration _config;
private readonly ModSettingApplier _modApplier;
private readonly ItemManager _items;
private readonly ICommandManager _commands;
private readonly MainWindow _mainWindow;
private readonly IChatGui _chat;
private readonly ActorManager _actors;
private readonly ObjectManager _objects;
private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly AutoDesignManager _autoDesignManager;
private readonly DesignManager _designManager;
private readonly DesignConverter _converter;
private readonly DesignFileSystem _designFileSystem;
private readonly Configuration _config;
private readonly ModSettingApplier _modApplier;
private readonly ItemManager _items;
private readonly RandomDesignGenerator _randomDesign;
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects,
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier,
ItemManager items)
ItemManager items, RandomDesignGenerator randomDesign)
{
_commands = commands;
_mainWindow = mainWindow;
@ -56,6 +58,7 @@ public class CommandService : IDisposable
_config = config;
_modApplier = modApplier;
_items = items;
_randomDesign = randomDesign;
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
_commands.AddHandler(ApplyCommandString,
@ -394,7 +397,8 @@ public class CommandService : IDisposable
if (_items.ItemData.Primary.TryGetValue(id, out var main))
items[0] = main;
}
else if (_items.ItemData.Primary.FindFirst(pair => string.Equals(pair.Value.Name, split[0], StringComparison.OrdinalIgnoreCase), out var i))
else if (_items.ItemData.Primary.FindFirst(pair => string.Equals(pair.Value.Name, split[0], StringComparison.OrdinalIgnoreCase),
out var i))
{
items[0] = i.Value;
}
@ -448,7 +452,8 @@ public class CommandService : IDisposable
var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (split.Length is not 2)
{
_chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ").AddYellow("[Design Name, Path or Identifier, or Clipboard]")
_chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ")
.AddYellow("[Design Name, Path or Identifier, Random, or Clipboard]")
.AddText(" | ")
.AddGreen("[Character Identifier]")
.AddText("; ")
@ -467,6 +472,13 @@ public class CommandService : IDisposable
.BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 ").AddYellow("Random").AddText(" supports the following restrictions:").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》》》 ").AddYellow("Random").AddText(", choosing a random design out of all your designs.").BuiltString);
_chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:{List of [text] or /[text]}").AddText(", containing a list of restrictions within swirly braces, separated by semicolons.").BuiltString);
_chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:[text]").AddText(", choosing a random design where the path, name or identifier contains 'text' (no brackets).").BuiltString);
_chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:/[text]").AddText(", choosing a random design where the path starts with 'text' (no brackets).").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 ").AddBlue("<Enable Mods>").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true")
.AddText(" or ").AddBlue("false").AddText(".").BuiltString);
@ -628,30 +640,57 @@ public class CommandService : IDisposable
return false;
}
private bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowClipboard)
private bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowSpecial)
{
design = null;
if (argument.Length == 0)
return false;
if (allowClipboard && string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase))
if (allowSpecial)
{
try
if (string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase))
{
var clipboardText = ImGui.GetClipboardText();
if (clipboardText.Length > 0)
design = _converter.FromBase64(clipboardText, true, true, out _);
}
catch
{
// ignored
try
{
var clipboardText = ImGui.GetClipboardText();
if (clipboardText.Length > 0)
design = _converter.FromBase64(clipboardText, true, true, out _);
}
catch
{
// ignored
}
if (design != null)
return true;
_chat.Print(new SeStringBuilder().AddText("Your current clipboard did not contain a valid design string.").BuiltString);
return false;
}
if (design != null)
if (argument.StartsWith(RandomString, StringComparison.OrdinalIgnoreCase))
{
try
{
if (argument.Length == RandomString.Length)
design = _randomDesign.Design();
else if (argument[RandomString.Length] == ':')
design = _randomDesign.Design(argument[(RandomString.Length + 1)..]);
if (design == null)
{
_chat.Print(new SeStringBuilder().AddText("No design matched your restrictions.").BuiltString);
return false;
}
_chat.Print($"Chose random design {((Design)design).Name}.");
}
catch (Exception ex)
{
_chat.Print(new SeStringBuilder().AddText($"Error in the restriction string: {ex.Message}").BuiltString);
return false;
}
return true;
_chat.Print(new SeStringBuilder().AddText("Your current clipboard did not contain a valid design string.").BuiltString);
return false;
}
}
if (Guid.TryParse(argument, out var guid))

View file

@ -149,7 +149,7 @@ public static class ServiceManagerA
.AddSingleton<DesignTab>()
.AddSingleton<QuickDesignCombo>()
.AddSingleton<LinkDesignCombo>()
.AddSingleton<RevertDesignCombo>()
.AddSingleton<SpecialDesignCombo>()
.AddSingleton<ModAssociationsTab>()
.AddSingleton<DesignDetailTab>()
.AddSingleton<UnlockTable>()

@ -1 +1 @@
Subproject commit 44021b93e6901c84b739bbf4d1c6350f4486cdbf
Subproject commit d0db2f1fbc3ce26d0756da5118157e5fc723c62f