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 class ApplicationTypeExtensions
{ {
public static readonly IReadOnlyList<(ApplicationType, string)> Types = new[] public static readonly IReadOnlyList<(ApplicationType, string)> Types =
{ [
(ApplicationType.Customizations, (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."), "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.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.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.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."), (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( 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) var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 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.Weapons) ? MetaFlag.WeaponState : 0)
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 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, customizeFlags, crestFlag, parameterFlags, metaFlag);
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest, return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,

View file

@ -1,7 +1,6 @@
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.State;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -12,20 +11,11 @@ public class AutoDesign
{ {
public const string RevertName = "Revert"; public const string RevertName = "Revert";
public Design? Design; public IDesignStandIn Design = new RevertDesign();
public JobGroup Jobs; public JobGroup Jobs;
public ApplicationType Type; public ApplicationType Type;
public short GearsetIndex = -1; 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() public AutoDesign Clone()
=> new() => new()
{ {
@ -50,12 +40,16 @@ public class AutoDesign
} }
public JObject Serialize() public JObject Serialize()
=> new() {
var ret = new JObject
{ {
["Design"] = Design?.Identifier.ToString(), ["Design"] = Design.SerializeName(),
["Type"] = (uint)Type, ["Type"] = (uint)Type,
["Conditions"] = CreateConditionObject(), ["Conditions"] = CreateConditionObject(),
}; };
Design.AddData(ret);
return ret;
}
private JObject CreateConditionObject() private JObject CreateConditionObject()
{ {

View file

@ -263,7 +263,7 @@ public sealed class AutoDesignApplier : IDisposable
return; return;
var mergedDesign = _designMerger.Merge( 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.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)); _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 SaveService _saveService;
private readonly JobService _jobs; private readonly JobService _jobs;
private readonly DesignManager _designs; private readonly DesignManager _designs;
private readonly ActorManager _actors; private readonly ActorManager _actors;
private readonly AutomationChanged _event; private readonly AutomationChanged _event;
private readonly DesignChanged _designEvent; private readonly DesignChanged _designEvent;
private readonly RandomDesignGenerator _randomDesigns;
private readonly List<AutoDesignSet> _data = []; private readonly List<AutoDesignSet> _data = [];
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = []; private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = [];
@ -34,14 +35,15 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
=> _enabled; => _enabled;
public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event, 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; _jobs = jobs;
_actors = actors; _actors = actors;
_saveService = saveService; _saveService = saveService;
_designs = designs; _designs = designs;
_event = @event; _event = @event;
_designEvent = designEvent; _designEvent = designEvent;
_randomDesigns = randomDesigns;
_designEvent.Subscribe(OnDesignChange, DesignChanged.Priority.AutoDesignManager); _designEvent.Subscribe(OnDesignChange, DesignChanged.Priority.AutoDesignManager);
Load(); Load();
migrator.ConsumeMigratedData(_actors, fileSystem, this); migrator.ConsumeMigratedData(_actors, fileSystem, this);
@ -227,7 +229,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase)); _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() var newDesign = new AutoDesign()
{ {
@ -238,7 +240,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
set.Designs.Add(newDesign); set.Designs.Add(newDesign);
Save(); Save();
Glamourer.Log.Debug( 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); _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)); _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) if (which >= set.Designs.Count || which < 0)
return; return;
var design = set.Designs[which]; var design = set.Designs[which];
if (design.Design?.Identifier == newDesign?.Identifier) if (design.Design.Equals(newDesign))
return; return;
var old = design.Design; var old = design.Design;
design.Design = newDesign; design.Design = newDesign;
Save(); Save();
Glamourer.Log.Debug( 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)); _event.Invoke(AutomationChanged.Type.ChangedDesign, set, (which, old, newDesign));
} }
@ -450,8 +452,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
continue; continue;
} }
var design = ToDesignObject(set.Name, j); if (ToDesignObject(set.Name, j) is { } design)
if (design != null)
set.Designs.Add(design); set.Designs.Add(design);
} }
} }
@ -459,10 +460,18 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
private AutoDesign? ToDesignObject(string setName, JObject jObj) private AutoDesign? ToDesignObject(string setName, JObject jObj)
{ {
var designIdentifier = jObj["Design"]?.ToObject<string?>(); var designIdentifier = jObj["Design"]?.ToObject<string?>();
Design? design = null; IDesignStandIn? design;
// designIdentifier == null means Revert-Design. // designIdentifier == null means Revert-Design for backwards compatibility
if (designIdentifier != null) 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) if (designIdentifier.Length == 0)
{ {
@ -480,14 +489,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
return null; return null;
} }
if (!_designs.Designs.TryGetValue(guid, out design)) if (!_designs.Designs.TryGetValue(guid, out var d))
{ {
Glamourer.Messager.NotificationMessage( Glamourer.Messager.NotificationMessage(
$"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", $"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.",
NotificationType.Warning); NotificationType.Warning);
return null; return null;
} }
design = d;
} }
design.ParseData(jObj);
// ApplicationType is a migration from an older property name. // ApplicationType is a migration from an older property name.
var applicationType = (ApplicationType)(jObj["Type"]?.ToObject<uint>() ?? jObj["ApplicationType"]?.ToObject<uint>() ?? 0); 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) public AutoDesignSet(string name, params ActorIdentifier[] identifiers)
: this(name, identifiers, new List<AutoDesign>()) : this(name, identifiers, [])
{ } { }
public enum Base : byte public enum Base : byte

View file

@ -1,15 +1,17 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Designs.Links; using Glamourer.Designs.Links;
using Glamourer.Interop.Material;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui.Classes; using OtterGui.Classes;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public sealed class Design : DesignBase, ISavable public sealed class Design : DesignBase, ISavable, IDesignStandIn
{ {
#region Data #region Data
@ -48,8 +50,36 @@ public sealed class Design : DesignBase, ISavable
public string Incognito public string Incognito
=> Identifier.ToString()[..8]; => Identifier.ToString()[..8];
public IEnumerable<(DesignBase? Design, ApplicationType Flags)> AllLinks public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks
=> LinkContainer.GetAllLinks(this).Select(t => ((DesignBase?)t.Link.Link, t.Link.Type)); => 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 #endregion

View file

@ -149,8 +149,11 @@ public class DesignColors : ISavable, IReadOnlyDictionary<string, uint>
Load(); Load();
} }
public uint GetColor(Design design) public uint GetColor(Design? design)
{ {
if (design == null)
return ColorId.NormalDesign.Value();
if (design.Color.Length == 0) if (design.Color.Length == 0)
return AutoColor(design); 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, ItemUnlockManager _itemUnlocks,
CustomizeUnlockManager _customizeUnlocks) : IService CustomizeUnlockManager _customizeUnlocks) : IService
{ {
public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, bool modAssociations) public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership,
=> Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations); 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) bool modAssociations)
{ {
var ret = new MergedDesign(designManager); var ret = new MergedDesign(designManager);
ret.Design.SetCustomize(_customize, currentCustomize); ret.Design.SetCustomize(_customize, currentCustomize);
CustomizeFlag fixFlags = 0; CustomizeFlag fixFlags = 0;
respectOwnership &= _config.UnlockedItemMode; respectOwnership &= _config.UnlockedItemMode;
@ -32,8 +34,8 @@ public class DesignMerger(
if (type is 0) if (type is 0)
continue; continue;
ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef(); ref readonly var data = ref design.GetDesignData(baseRef);
var source = design == null ? StateSource.Game : StateSource.Manual; var source = design.AssociatedSource();
if (!data.IsHuman) if (!data.IsHuman)
continue; 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; return;
var materials = ret.Design.GetMaterialDataRef(); var materials = ret.Design.GetMaterialDataRef();
foreach (var (key, value) in design.Materials.Where(p => p.Item2.Enabled)) foreach (var (key, value) in design.Materials.Where(p => p.Item2.Enabled))
materials.TryAddValue(MaterialValueIndex.FromKey(key), value); materials.TryAddValue(MaterialValueIndex.FromKey(key), value);
@ -243,7 +246,7 @@ public class DesignMerger(
ret.Sources[CustomizeIndex.Face] = source; 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; var face = customize.Face;
foreach (var index in Enum.GetValues<CustomizeIndex>()) 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"/> /// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnDesignChange"/>
DesignFileSystemSelector = -1, DesignFileSystemSelector = -1,
/// <seealso cref="RevertDesignCombo.OnDesignChange"/> /// <seealso cref="SpecialDesignCombo.OnDesignChange"/>
DesignCombo = -2, DesignCombo = -2,
} }
} }

View file

@ -3,26 +3,24 @@ using Dalamud.Interface.Utility.Raii;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Log; using OtterGui.Log;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui; 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 EphemeralConfig _config;
private readonly DesignChanged _designChanged; private readonly DesignChanged _designChanged;
private readonly DesignColors _designColors; private readonly DesignColors _designColors;
protected readonly TabSelected TabSelected; protected readonly TabSelected TabSelected;
protected float InnerWidth; 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) TabSelected tabSelected, EphemeralConfig config, DesignColors designColors)
: base(generator, MouseWheelType.Control, log) : base(generator, MouseWheelType.Control, log)
{ {
@ -46,28 +44,34 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
{ {
var (design, path) = Items[globalIdx]; var (design, path) = Items[globalIdx];
bool ret; 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); 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; return ret;
} }
@ -79,22 +83,22 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
return CurrentSelectionIdx; return CurrentSelectionIdx;
} }
protected bool Draw(Design? currentDesign, string? label, float width) protected bool Draw(IDesignStandIn? currentDesign, string? label, float width)
{ {
_currentDesign = currentDesign; _currentDesign = currentDesign;
InnerWidth = 400 * ImGuiHelpers.GlobalScale; InnerWidth = 400 * ImGuiHelpers.GlobalScale;
var name = label ?? "Select Design Here..."; var name = label ?? "Select Design Here...";
bool ret; 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()) ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing())
&& CurrentSelection != null; && CurrentSelection != null;
} }
if (currentDesign != null) if (currentDesign is Design design)
{ {
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) 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."); ImGuiUtil.HoverTooltip("Control + Right-Click to move to design.");
} }
@ -102,8 +106,8 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
return ret; return ret;
} }
protected override string ToString(Tuple<Design, string> obj) protected override string ToString(Tuple<IDesignStandIn, string> obj)
=> obj.Item1.Name.Text; => obj.Item1.ResolveName(Incognito);
protected override float GetFilterWidth() protected override float GetFilterWidth()
=> InnerWidth - 2 * ImGui.GetStyle().FramePadding.X; => 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) protected override bool IsVisible(int globalIndex, LowerString filter)
{ {
var (design, path) = Items[globalIndex]; 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) 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 public abstract class DesignCombo : DesignComboBase
{ {
protected DesignCombo(Logger log, DesignChanged designChanged, TabSelected tabSelected, 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) : base(generator, log, designChanged, tabSelected, config, designColors)
{ {
if (Items.Count == 0) if (Items.Count == 0)
@ -162,11 +166,11 @@ public abstract class DesignCombo : DesignComboBase
base.Cleanup(); base.Cleanup();
} }
public Design? Design public IDesignStandIn? Design
=> CurrentSelection?.Item1; => CurrentSelection?.Item1;
public void Draw(float width) 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 public sealed class QuickDesignCombo : DesignCombo
@ -182,7 +186,7 @@ public sealed class QuickDesignCombo : DesignCombo
[ [
.. designs.Designs .. designs.Designs
.Where(d => d.QuickDesign) .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), .OrderBy(d => d.Item2),
]) ])
=> AllowMouseWheel = MouseWheelType.Unmodified; => AllowMouseWheel = MouseWheelType.Unmodified;
@ -199,51 +203,35 @@ public sealed class LinkDesignCombo(
: DesignCombo(log, designChanged, tabSelected, config, designColors, () => : DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
[ [
.. designs.Designs .. 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), .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) 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; return;
if (autoDesignIndex >= 0) if (autoDesignIndex >= 0)
_autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1);
else 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) private void DrawApplyButton(Vector2 size)
{ {
var design = _designCombo.Design; var design = _designCombo.Design as Design;
var available = 0; var available = 0;
var tooltip = string.Empty; var tooltip = string.Empty;
if (design == null) if (design == null)
@ -135,7 +135,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (_playerIdentifier.IsValid && _playerData.Valid) if (_playerIdentifier.IsValid && _playerData.Valid)
{ {
available |= 1; 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) if (_targetIdentifier.IsValid && _targetData.Valid)
@ -143,7 +143,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (available != 0) if (available != 0)
tooltip += '\n'; tooltip += '\n';
available |= 2; 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) if (available == 0)
@ -157,7 +157,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (state == null && !_stateManager.GetOrCreate(id, data.Objects[0], out state)) 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; return;
} }

View file

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

View file

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

View file

@ -19,28 +19,30 @@ namespace Glamourer.Services;
public class CommandService : IDisposable public class CommandService : IDisposable
{ {
private const string RandomString = "random";
private const string MainCommandString = "/glamourer"; private const string MainCommandString = "/glamourer";
private const string ApplyCommandString = "/glamour"; private const string ApplyCommandString = "/glamour";
private readonly ICommandManager _commands; private readonly ICommandManager _commands;
private readonly MainWindow _mainWindow; private readonly MainWindow _mainWindow;
private readonly IChatGui _chat; private readonly IChatGui _chat;
private readonly ActorManager _actors; private readonly ActorManager _actors;
private readonly ObjectManager _objects; private readonly ObjectManager _objects;
private readonly StateManager _stateManager; private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly AutoDesignManager _autoDesignManager; private readonly AutoDesignManager _autoDesignManager;
private readonly DesignManager _designManager; private readonly DesignManager _designManager;
private readonly DesignConverter _converter; private readonly DesignConverter _converter;
private readonly DesignFileSystem _designFileSystem; private readonly DesignFileSystem _designFileSystem;
private readonly Configuration _config; private readonly Configuration _config;
private readonly ModSettingApplier _modApplier; private readonly ModSettingApplier _modApplier;
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly RandomDesignGenerator _randomDesign;
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects,
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier,
ItemManager items) ItemManager items, RandomDesignGenerator randomDesign)
{ {
_commands = commands; _commands = commands;
_mainWindow = mainWindow; _mainWindow = mainWindow;
@ -56,6 +58,7 @@ public class CommandService : IDisposable
_config = config; _config = config;
_modApplier = modApplier; _modApplier = modApplier;
_items = items; _items = items;
_randomDesign = randomDesign;
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
_commands.AddHandler(ApplyCommandString, _commands.AddHandler(ApplyCommandString,
@ -394,7 +397,8 @@ public class CommandService : IDisposable
if (_items.ItemData.Primary.TryGetValue(id, out var main)) if (_items.ItemData.Primary.TryGetValue(id, out var main))
items[0] = 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; items[0] = i.Value;
} }
@ -448,7 +452,8 @@ public class CommandService : IDisposable
var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (split.Length is not 2) 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(" | ") .AddText(" | ")
.AddGreen("[Character Identifier]") .AddGreen("[Character Identifier]")
.AddText("; ") .AddText("; ")
@ -467,6 +472,13 @@ public class CommandService : IDisposable
.BuiltString); .BuiltString);
_chat.Print(new SeStringBuilder() _chat.Print(new SeStringBuilder()
.AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString); .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() _chat.Print(new SeStringBuilder()
.AddText(" 》 ").AddBlue("<Enable Mods>").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true") .AddText(" 》 ").AddBlue("<Enable Mods>").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true")
.AddText(" or ").AddBlue("false").AddText(".").BuiltString); .AddText(" or ").AddBlue("false").AddText(".").BuiltString);
@ -628,30 +640,57 @@ public class CommandService : IDisposable
return false; 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; design = null;
if (argument.Length == 0) if (argument.Length == 0)
return false; return false;
if (allowClipboard && string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase)) if (allowSpecial)
{ {
try if (string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase))
{ {
var clipboardText = ImGui.GetClipboardText(); try
if (clipboardText.Length > 0) {
design = _converter.FromBase64(clipboardText, true, true, out _); var clipboardText = ImGui.GetClipboardText();
} if (clipboardText.Length > 0)
catch design = _converter.FromBase64(clipboardText, true, true, out _);
{ }
// ignored 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; 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)) if (Guid.TryParse(argument, out var guid))

View file

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

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