Add overrides to associated collections.

This commit is contained in:
Ottermandias 2024-02-14 15:42:08 +01:00
parent 02dff90dd0
commit a194f88903
10 changed files with 322 additions and 30 deletions

View file

@ -9,6 +9,7 @@ using Glamourer.Gui.Tabs.AutomationTab;
using Glamourer.Gui.Tabs.DebugTab;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Gui.Tabs.NpcTab;
using Glamourer.Gui.Tabs.SettingsTab;
using Glamourer.Gui.Tabs.UnlocksTab;
using ImGuiNET;
using OtterGui.Custom;

View file

@ -0,0 +1,122 @@
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Style;
using Glamourer.Interop;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.GameData.Actors;
namespace Glamourer.Gui.Tabs.SettingsTab;
public class CollectionOverrideDrawer(
CollectionOverrideService collectionOverrides,
Configuration config,
ObjectManager objects,
ActorManager actors) : IService
{
private string _newIdentifier = string.Empty;
private ActorIdentifier[] _identifiers = [];
private int _dragDropIndex = -1;
private Exception? _exception;
private string _collection = string.Empty;
public void Draw()
{
using var header = ImRaii.CollapsingHeader("Collection Overrides");
ImGuiUtil.HoverTooltip(
"Here you can set up overrides for Penumbra collections that should have their settings changed when automatically applying mod settings from a design.\n"
+ "Instead of the collection associated with the overridden character, the overridden collection will be manipulated.");
if (!header)
return;
using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg);
if (!table)
return;
var buttonSize = new Vector2(ImGui.GetFrameHeight());
ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X);
ImGui.TableSetupColumn("identifiers", ImGuiTableColumnFlags.WidthStretch, 0.6f);
ImGui.TableSetupColumn("collections", ImGuiTableColumnFlags.WidthStretch, 0.4f);
for (var i = 0; i < collectionOverrides.Overrides.Count; ++i)
{
var (identifier, collection) = collectionOverrides.Overrides[i];
using var id = ImRaii.PushId(i);
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true))
collectionOverrides.DeleteOverride(i--);
ImGui.TableNextColumn();
ImGui.Selectable(config.Ephemeral.IncognitoMode ? identifier.Incognito(null) : identifier.ToString());
using (var target = ImRaii.DragDropTarget())
{
if (target.Success && ImGuiUtil.IsDropping("DraggingOverride"))
{
collectionOverrides.MoveOverride(_dragDropIndex, i);
_dragDropIndex = -1;
}
}
using (var source = ImRaii.DragDropSource())
{
if (source)
{
ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0);
ImGui.TextUnformatted($"Reordering Override #{i + 1}...");
_dragDropIndex = i;
}
}
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputText("##input", ref collection, 64) && collection.Length > 0)
collectionOverrides.ChangeOverride(i, collection);
}
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PersonCirclePlus.ToIconString(), buttonSize, "Add override for current player.",
!objects.Player.Valid, true))
collectionOverrides.AddOverride([objects.PlayerData.Identifier], _collection.Length > 0 ? _collection : "TempCollection");
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemInnerSpacing.X - buttonSize.X * 2);
if (ImGui.InputTextWithHint("##newActor", "New Identifier...", ref _newIdentifier, 80))
try
{
_identifiers = actors.FromUserString(_newIdentifier, false);
}
catch (ActorIdentifierFactory.IdentifierParseError e)
{
_exception = e;
_identifiers = [];
}
var tt = _identifiers.Any(i => i.IsValid)
? $"Add a new override for {_identifiers.First(i => i.IsValid)}."
: _newIdentifier.Length == 0
? "Please enter an identifier string first."
: $"The identifier string {_newIdentifier} does not result in a valid identifier{(_exception == null ? "." : $":\n\n{_exception?.Message}")}";
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is 'T', true))
collectionOverrides.AddOverride(_identifiers, _collection.Length > 0 ? _collection : "TempCollection");
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
using (ImRaii.PushFont(UiBuilder.IconFont))
{
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
ActorIdentifierFactory.WriteUserStringTooltip(false);
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.InputTextWithHint("##collection", "Enter Collection...", ref _collection, 80);
}
}

View file

@ -15,7 +15,7 @@ using OtterGui;
using OtterGui.Raii;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs;
namespace Glamourer.Gui.Tabs.SettingsTab;
public class SettingsTab(
Configuration config,
@ -29,7 +29,8 @@ public class SettingsTab(
IKeyState keys,
DesignColorUi designColorUi,
PaletteImport paletteImport,
PalettePlusChecker paletteChecker)
PalettePlusChecker paletteChecker,
CollectionOverrideDrawer overrides)
: ITab
{
private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray();
@ -57,6 +58,7 @@ public class SettingsTab(
DrawBehaviorSettings();
DrawInterfaceSettings();
DrawColorSettings();
overrides.Draw();
DrawCodes();
}
@ -90,6 +92,9 @@ public class SettingsTab(
"Enable the display and editing of advanced customization options like arbitrary colors.",
config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters);
PaletteImportButton();
Checkbox("Enable Advanced Dye Options",
"Enable the display and editing of advanced dyes (color sets) for all equipment",
config.UseAdvancedDyes, v => config.UseAdvancedDyes = v);
Checkbox("Always Apply Associated Mods",
"Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n"
+ "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"
@ -189,6 +194,7 @@ public class SettingsTab(
ImGui.NewLine();
}
private void PaletteImportButton()
{
if (!config.UseAdvancedParameters || !config.ShowPalettePlusImport)

View file

@ -1,11 +1,13 @@
using Glamourer.Designs.Links;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
using OtterGui.Services;
namespace Glamourer.Interop.Penumbra;
public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects) : IService
public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects, CollectionOverrideService overrides)
: IService
{
public void HandleStateApplication(ActorState state, MergedDesign design)
{
@ -24,7 +26,7 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
foreach (var actor in data.Objects)
{
var collection = penumbra.GetActorCollection(actor);
var (collection, overridden) = overrides.GetCollection(actor, state.Identifier);
if (collection.Length == 0)
{
Glamourer.Log.Verbose($"[Mod Applier] Could not obtain associated collection for {actor.Utf8Name}.");
@ -40,16 +42,17 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
if (message.Length > 0)
Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}");
else
Glamourer.Log.Verbose($"[Mod Applier] Set mod settings for {mod.DirectoryName} in {collection}.");
Glamourer.Log.Verbose(
$"[Mod Applier] Set mod settings for {mod.DirectoryName} in {collection}{(overridden ? " (overridden by settings)" : string.Empty)}.");
}
}
}
public (List<string> Messages, int Applied, string Collection) ApplyModSettings(IReadOnlyDictionary<Mod, ModSettings> settings, Actor actor)
public (List<string> Messages, int Applied, string Collection, bool Overridden) ApplyModSettings(IReadOnlyDictionary<Mod, ModSettings> settings, Actor actor)
{
var collection = penumbra.GetActorCollection(actor);
var (collection, overridden) = overrides.GetCollection(actor);
if (collection.Length <= 0)
return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty);
return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty, false);
var messages = new List<string>();
var appliedMods = 0;
@ -62,6 +65,6 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
++appliedMods;
}
return (messages, appliedMods, collection);
return (messages, appliedMods, collection, overridden);
}
}

View file

@ -0,0 +1,160 @@
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Filesystem;
using OtterGui.Services;
using Penumbra.GameData.Actors;
namespace Glamourer.Services;
public sealed class CollectionOverrideService : IService, ISavable
{
public const int Version = 1;
private readonly SaveService _saveService;
private readonly ActorManager _actors;
private readonly PenumbraService _penumbra;
public CollectionOverrideService(SaveService saveService, ActorManager actors, PenumbraService penumbra)
{
_saveService = saveService;
_actors = actors;
_penumbra = penumbra;
Load();
}
public unsafe (string Collection, bool Overriden) GetCollection(Actor actor, ActorIdentifier identifier = default)
{
if (!identifier.IsValid)
identifier = _actors.FromObject(actor.AsObject, out _, true, true, true);
return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret)
? (ret.Collection, true)
: (_penumbra.GetActorCollection(actor), false);
}
private readonly List<(ActorIdentifier Actor, string Collection)> _overrides = [];
public IReadOnlyList<(ActorIdentifier Actor, string Collection)> Overrides
=> _overrides;
public string ToFilename(FilenameService fileNames)
=> fileNames.CollectionOverrideFile;
public void AddOverride(IEnumerable<ActorIdentifier> identifiers, string collection)
{
if (collection.Length == 0)
return;
foreach (var id in identifiers.Where(i => i.IsValid))
{
_overrides.Add((id, collection));
Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collection}.");
_saveService.QueueSave(this);
}
}
public void ChangeOverride(int idx, string newCollection)
{
if (idx < 0 || idx >= _overrides.Count || newCollection.Length == 0)
return;
var current = _overrides[idx];
if (current.Collection == newCollection)
return;
_overrides[idx] = current with { Collection = newCollection };
Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.Collection} to {newCollection}.");
_saveService.QueueSave(this);
}
public void DeleteOverride(int idx)
{
if (idx < 0 || idx >= _overrides.Count)
return;
_overrides.RemoveAt(idx);
Glamourer.Log.Debug($"Removed collection override {idx + 1}.");
_saveService.QueueSave(this);
}
public void MoveOverride(int idxFrom, int idxTo)
{
if (!_overrides.Move(idxFrom, idxTo))
return;
Glamourer.Log.Debug($"Moved collection override {idxFrom + 1} to {idxTo + 1}.");
_saveService.QueueSave(this);
}
private void Load()
{
var file = _saveService.FileNames.CollectionOverrideFile;
if (!File.Exists(file))
return;
try
{
var text = File.ReadAllText(file);
var jObj = JObject.Parse(text);
var version = jObj["Version"]?.ToObject<int>() ?? 0;
switch (version)
{
case 1:
if (jObj["Overrides"] is not JArray array)
{
Glamourer.Log.Error($"Invalid format of collection override file, ignored.");
return;
}
foreach (var token in array.OfType<JObject>())
{
var collection = token["Collection"]?.ToObject<string>() ?? string.Empty;
var identifier = _actors.FromJson(token);
if (!identifier.IsValid)
Glamourer.Log.Warning($"Invalid identifier for collection override with collection [{collection}], skipped.");
else if (collection.Length == 0)
Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped.");
else
_overrides.Add((identifier, collection));
}
break;
default:
Glamourer.Log.Error($"Invalid version {version} of collection override file, ignored.");
return;
}
}
catch (Exception ex)
{
Glamourer.Log.Error($"Error loading collection override file:\n{ex}");
}
}
public void Save(StreamWriter writer)
{
var jObj = new JObject()
{
["Version"] = Version,
["Overrides"] = SerializeOverrides(),
};
using var j = new JsonTextWriter(writer);
j.Formatting = Formatting.Indented;
jObj.WriteTo(j);
return;
JArray SerializeOverrides()
{
var jArray = new JArray();
foreach (var (actor, collection) in _overrides)
{
var obj = actor.ToJson();
obj["Collection"] = collection;
jArray.Add(obj);
}
return jArray;
}
}
}

View file

@ -440,13 +440,13 @@ public class CommandService : IDisposable
if (!applyMods || design is not Design d)
return;
var (messages, appliedMods, collection) = _modApplier.ApplyModSettings(d.AssociatedMods, actor);
var (messages, appliedMods, collection, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor);
foreach (var message in messages)
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");
if (appliedMods > 0)
Glamourer.Messager.Chat.Print($"Applied {appliedMods} mod settings to {collection}.");
Glamourer.Messager.Chat.Print($"Applied {appliedMods} mod settings to {collection}{(overridden ? " (overridden by settings)" : string.Empty)}.");
}
private bool Delete(string argument)

View file

@ -17,21 +17,23 @@ public class FilenameService
public readonly string DesignColorFile;
public readonly string EphemeralConfigFile;
public readonly string NpcAppearanceFile;
public readonly string CollectionOverrideFile;
public FilenameService(DalamudPluginInterface pi)
{
ConfigDirectory = pi.ConfigDirectory.FullName;
ConfigFile = pi.ConfigFile.FullName;
AutomationFile = Path.Combine(ConfigDirectory, "automation.json");
DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json");
MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json");
UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json");
UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json");
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json");
DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json");
EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json");
NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json");
ConfigDirectory = pi.ConfigDirectory.FullName;
ConfigFile = pi.ConfigFile.FullName;
AutomationFile = Path.Combine(ConfigDirectory, "automation.json");
DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json");
MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json");
UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json");
UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json");
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json");
DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json");
EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json");
NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json");
CollectionOverrideFile = Path.Combine(ConfigDirectory, "collection_overrides.json");
}
public IEnumerable<FileInfo> Designs()

View file

@ -12,6 +12,7 @@ using Glamourer.Gui.Tabs.AutomationTab;
using Glamourer.Gui.Tabs.DebugTab;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Gui.Tabs.NpcTab;
using Glamourer.Gui.Tabs.SettingsTab;
using Glamourer.Gui.Tabs.UnlocksTab;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;

View file

@ -7,13 +7,10 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Services;
public sealed class TextureService : TextureCache, IDisposable
public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider)
: TextureCache(dataManager, textureProvider), IDisposable
{
public TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider)
: base(dataManager, textureProvider)
=> _slotIcons = CreateSlotIcons(uiBuilder);
private readonly IDalamudTextureWrap?[] _slotIcons;
private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder);
public (nint, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot)
{

@ -1 +1 @@
Subproject commit 3a7f6d86c9975a4892f58be3c629b7664e6c3733
Subproject commit 5825bafb0602cf8a252581f21291c0d27e5561f0