Add initial support for setting temporary mod settings.

This commit is contained in:
Ottermandias 2024-12-31 16:35:08 +01:00
parent e41755ed7e
commit 24452f3c79
22 changed files with 394 additions and 202 deletions

View file

@ -293,6 +293,8 @@ public sealed class AutoDesignApplier : IDisposable
set.Designs.Where(d => d.IsActive(actor)) set.Designs.Where(d => d.IsActive(actor))
.SelectMany(d => d.Design.AllLinks(newApplication).Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), .SelectMany(d => d.Design.AllLinks(newApplication).Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))),
state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods);
if (set.ResetTemporarySettings)
mergedDesign.ResetTemporarySettings = true;
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false));
forcedRedraw = mergedDesign.ForcedRedraw; forcedRedraw = mergedDesign.ForcedRedraw;

View file

@ -444,8 +444,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
var set = new AutoDesignSet(name, group) var set = new AutoDesignSet(name, group)
{ {
Enabled = obj["Enabled"]?.ToObject<bool>() ?? false, Enabled = obj["Enabled"]?.ToObject<bool>() ?? false,
BaseState = obj["BaseState"]?.ToObject<AutoDesignSet.Base>() ?? AutoDesignSet.Base.Current, ResetTemporarySettings = obj["ResetTemporarySettings"]?.ToObject<bool>() ?? false,
BaseState = obj["BaseState"]?.ToObject<AutoDesignSet.Base>() ?? AutoDesignSet.Base.Current,
}; };
if (set.Enabled) if (set.Enabled)
@ -602,9 +603,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
? ActorIdentifier.RetainerType.Mannequin ? ActorIdentifier.RetainerType.Mannequin
: ActorIdentifier.RetainerType.Bell).CreatePermanent(), : ActorIdentifier.RetainerType.Bell).CreatePermanent(),
], ],
IdentifierType.Npc => CreateNpcs(_actors, identifier), IdentifierType.Npc => CreateNpcs(_actors, identifier),
IdentifierType.Owned => CreateNpcs(_actors, identifier), IdentifierType.Owned => CreateNpcs(_actors, identifier),
_ => [], _ => [],
}; };
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier) static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)

View file

@ -10,7 +10,8 @@ public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List<Auto
public string Name = name; public string Name = name;
public ActorIdentifier[] Identifiers = identifiers; public ActorIdentifier[] Identifiers = identifiers;
public bool Enabled; public bool Enabled;
public Base BaseState = Base.Current; public Base BaseState = Base.Current;
public bool ResetTemporarySettings = false;
public JObject Serialize() public JObject Serialize()
{ {
@ -20,11 +21,12 @@ public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List<Auto
return new JObject() return new JObject()
{ {
["Name"] = Name, ["Name"] = Name,
["Identifier"] = Identifiers[0].ToJson(), ["Identifier"] = Identifiers[0].ToJson(),
["Enabled"] = Enabled, ["Enabled"] = Enabled,
["BaseState"] = BaseState.ToString(), ["BaseState"] = BaseState.ToString(),
["Designs"] = list, ["ResetTemporarySettings"] = ResetTemporarySettings.ToString(),
["Designs"] = list,
}; };
} }

View file

@ -27,9 +27,10 @@ public enum HeightDisplayType
public class DefaultDesignSettings public class DefaultDesignSettings
{ {
public bool AlwaysForceRedrawing = false; public bool AlwaysForceRedrawing = false;
public bool ResetAdvancedDyes = false; public bool ResetAdvancedDyes = false;
public bool ShowQuickDesignBar = true; public bool ShowQuickDesignBar = true;
public bool ResetTemporarySettings = false;
} }
public class Configuration : IPluginConfiguration, ISavable public class Configuration : IPluginConfiguration, ISavable
@ -63,6 +64,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool ShowColorConfig { get; set; } = true; public bool ShowColorConfig { get; set; } = true;
public bool ChangeEntireItem { get; set; } = false; public bool ChangeEntireItem { get; set; } = false;
public bool AlwaysApplyAssociatedMods { get; set; } = false; public bool AlwaysApplyAssociatedMods { get; set; } = false;
public bool UseTemporarySettings { get; set; } = true;
public bool AllowDoubleClickToApply { get; set; } = false; public bool AllowDoubleClickToApply { get; set; } = false;
public bool RespectManualOnAutomationUpdate { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false;

View file

@ -28,32 +28,34 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
internal Design(Design other) internal Design(Design other)
: base(other) : base(other)
{ {
Tags = [.. other.Tags]; Tags = [.. other.Tags];
Description = other.Description; Description = other.Description;
QuickDesign = other.QuickDesign; QuickDesign = other.QuickDesign;
ForcedRedraw = other.ForcedRedraw; ForcedRedraw = other.ForcedRedraw;
ResetAdvancedDyes = other.ResetAdvancedDyes; ResetAdvancedDyes = other.ResetAdvancedDyes;
Color = other.Color; ResetTemporarySettings = other.ResetTemporarySettings;
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods); Color = other.Color;
Links = Links.Clone(); AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
Links = Links.Clone();
} }
// Metadata // Metadata
public new const int FileVersion = 2; public new const int FileVersion = 2;
public Guid Identifier { get; internal init; } public Guid Identifier { get; internal init; }
public DateTimeOffset CreationDate { get; internal init; } public DateTimeOffset CreationDate { get; internal init; }
public DateTimeOffset LastEdit { get; internal set; } public DateTimeOffset LastEdit { get; internal set; }
public LowerString Name { get; internal set; } = LowerString.Empty; public LowerString Name { get; internal set; } = LowerString.Empty;
public string Description { get; internal set; } = string.Empty; public string Description { get; internal set; } = string.Empty;
public string[] Tags { get; internal set; } = []; public string[] Tags { get; internal set; } = [];
public int Index { get; internal set; } public int Index { get; internal set; }
public bool ForcedRedraw { get; internal set; } public bool ForcedRedraw { get; internal set; }
public bool ResetAdvancedDyes { get; internal set; } public bool ResetAdvancedDyes { get; internal set; }
public bool QuickDesign { get; internal set; } = true; public bool ResetTemporarySettings { get; internal set; }
public string Color { get; internal set; } = string.Empty; public bool QuickDesign { get; internal set; } = true;
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = []; public string Color { get; internal set; } = string.Empty;
public LinkContainer Links { get; private set; } = []; public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
public LinkContainer Links { get; private set; } = [];
public string Incognito public string Incognito
=> Identifier.ToString()[..8]; => Identifier.ToString()[..8];
@ -100,25 +102,26 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
{ {
var ret = new JObject() var ret = new JObject()
{ {
["FileVersion"] = FileVersion, ["FileVersion"] = FileVersion,
["Identifier"] = Identifier, ["Identifier"] = Identifier,
["CreationDate"] = CreationDate, ["CreationDate"] = CreationDate,
["LastEdit"] = LastEdit, ["LastEdit"] = LastEdit,
["Name"] = Name.Text, ["Name"] = Name.Text,
["Description"] = Description, ["Description"] = Description,
["ForcedRedraw"] = ForcedRedraw, ["ForcedRedraw"] = ForcedRedraw,
["ResetAdvancedDyes"] = ResetAdvancedDyes, ["ResetAdvancedDyes"] = ResetAdvancedDyes,
["Color"] = Color, ["ResetTemporarySettings"] = ResetTemporarySettings,
["QuickDesign"] = QuickDesign, ["Color"] = Color,
["Tags"] = JArray.FromObject(Tags), ["QuickDesign"] = QuickDesign,
["WriteProtected"] = WriteProtected(), ["Tags"] = JArray.FromObject(Tags),
["Equipment"] = SerializeEquipment(), ["WriteProtected"] = WriteProtected(),
["Bonus"] = SerializeBonusItems(), ["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(), ["Bonus"] = SerializeBonusItems(),
["Parameters"] = SerializeParameters(), ["Customize"] = SerializeCustomize(),
["Materials"] = SerializeMaterials(), ["Parameters"] = SerializeParameters(),
["Mods"] = SerializeMods(), ["Materials"] = SerializeMaterials(),
["Links"] = Links.Serialize(), ["Mods"] = SerializeMods(),
["Links"] = Links.Serialize(),
}; };
return ret; return ret;
} }
@ -250,9 +253,10 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
LoadParameters(json["Parameters"], design, design.Name); LoadParameters(json["Parameters"], design, design.Name);
LoadMaterials(json["Materials"], design, design.Name); LoadMaterials(json["Materials"], design, design.Name);
LoadLinks(linkLoader, json["Links"], design); LoadLinks(linkLoader, json["Links"], design);
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty; design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
design.ForcedRedraw = json["ForcedRedraw"]?.ToObject<bool>() ?? false; design.ForcedRedraw = json["ForcedRedraw"]?.ToObject<bool>() ?? false;
design.ResetAdvancedDyes = json["ResetAdvancedDyes"]?.ToObject<bool>() ?? false; design.ResetAdvancedDyes = json["ResetAdvancedDyes"]?.ToObject<bool>() ?? false;
design.ResetTemporarySettings = json["ResetTemporarySettings"]?.ToObject<bool>() ?? false;
return design; return design;
static string[] ParseTags(JObject json) static string[] ParseTags(JObject json)
@ -278,12 +282,15 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
continue; continue;
} }
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, List<string>>>() ?? []; var forceInherit = tok["Inherit"]?.ToObject<bool>() ?? false;
var settings = new Dictionary<string, List<string>>(settingsDict.Count); var removeSetting = tok["Remove"]?.ToObject<bool>() ?? false;
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, List<string>>>() ?? [];
var settings = new Dictionary<string, List<string>>(settingsDict.Count);
foreach (var (key, value) in settingsDict) foreach (var (key, value) in settingsDict)
settings.Add(key, value); settings.Add(key, value);
var priority = tok["Priority"]?.ToObject<int>() ?? 0; var priority = tok["Priority"]?.ToObject<int>() ?? 0;
if (!design.AssociatedMods.TryAdd(new Mod(name, directory), new ModSettings(settings, priority, enabled.Value))) if (!design.AssociatedMods.TryAdd(new Mod(name, directory),
new ModSettings(settings, priority, enabled.Value, forceInherit, removeSetting)))
Glamourer.Messager.NotificationMessage("The loaded design contains a mod more than once, skipped.", NotificationType.Warning); Glamourer.Messager.NotificationMessage("The loaded design contains a mod more than once, skipped.", NotificationType.Warning);
} }
} }

View file

@ -99,14 +99,15 @@ public sealed class DesignManager : DesignEditor
var (actualName, path) = ParseName(name, handlePath); var (actualName, path) = ParseName(name, handlePath);
var design = new Design(Customizations, Items) var design = new Design(Customizations, Items)
{ {
CreationDate = DateTimeOffset.UtcNow, CreationDate = DateTimeOffset.UtcNow,
LastEdit = DateTimeOffset.UtcNow, LastEdit = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(), Identifier = CreateNewGuid(),
Name = actualName, Name = actualName,
Index = Designs.Count, Index = Designs.Count,
ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing,
ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes,
QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar,
ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings,
}; };
Designs.Add(design); Designs.Add(design);
Glamourer.Log.Debug($"Added new design {design.Identifier}."); Glamourer.Log.Debug($"Added new design {design.Identifier}.");
@ -121,14 +122,15 @@ public sealed class DesignManager : DesignEditor
var (actualName, path) = ParseName(name, handlePath); var (actualName, path) = ParseName(name, handlePath);
var design = new Design(clone) var design = new Design(clone)
{ {
CreationDate = DateTimeOffset.UtcNow, CreationDate = DateTimeOffset.UtcNow,
LastEdit = DateTimeOffset.UtcNow, LastEdit = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(), Identifier = CreateNewGuid(),
Name = actualName, Name = actualName,
Index = Designs.Count, Index = Designs.Count,
ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing,
ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes,
QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar,
ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings,
}; };
Designs.Add(design); Designs.Add(design);
@ -144,11 +146,11 @@ public sealed class DesignManager : DesignEditor
var (actualName, path) = ParseName(name, handlePath); var (actualName, path) = ParseName(name, handlePath);
var design = new Design(clone) var design = new Design(clone)
{ {
CreationDate = DateTimeOffset.UtcNow, CreationDate = DateTimeOffset.UtcNow,
LastEdit = DateTimeOffset.UtcNow, LastEdit = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(), Identifier = CreateNewGuid(),
Name = actualName, Name = actualName,
Index = Designs.Count, Index = Designs.Count,
}; };
Designs.Add(design); Designs.Add(design);
Glamourer.Log.Debug( Glamourer.Log.Debug(
@ -351,6 +353,17 @@ public sealed class DesignManager : DesignEditor
DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null); DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null);
} }
public void ChangeResetTemporarySettings(Design design, bool resetTemporarySettings)
{
if (design.ResetTemporarySettings == resetTemporarySettings)
return;
design.ResetTemporarySettings = resetTemporarySettings;
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set {design.Identifier} to {(resetTemporarySettings ? string.Empty : "not")} reset temporary settings.");
DesignChanged.Invoke(DesignChanged.Type.ResetTemporarySettings, design, null);
}
/// <summary> Change whether to apply a specific customize value. </summary> /// <summary> Change whether to apply a specific customize value. </summary>
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value) public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
{ {

View file

@ -26,5 +26,6 @@ public interface IDesignStandIn : IEquatable<IDesignStandIn>
public bool ForcedRedraw { get; } public bool ForcedRedraw { get; }
public bool ResetAdvancedDyes { get; } public bool ResetAdvancedDyes { get; }
public bool ResetTemporarySettings { get; }
} }

View file

@ -58,6 +58,8 @@ public class DesignMerger(
ret.ForcedRedraw = true; ret.ForcedRedraw = true;
if (design.ResetAdvancedDyes) if (design.ResetAdvancedDyes)
ret.ResetAdvancedDyes = true; ret.ResetAdvancedDyes = true;
if (design.ResetTemporarySettings)
ret.ResetTemporarySettings = true;
} }
ApplyFixFlags(ret, fixFlags); ApplyFixFlags(ret, fixFlags);

View file

@ -101,4 +101,5 @@ public sealed class MergedDesign
public StateSources Sources = new(); public StateSources Sources = new();
public bool ForcedRedraw; public bool ForcedRedraw;
public bool ResetAdvancedDyes; public bool ResetAdvancedDyes;
public bool ResetTemporarySettings;
} }

View file

@ -56,4 +56,7 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ
public bool ResetAdvancedDyes public bool ResetAdvancedDyes
=> combo.Design?.ResetAdvancedDyes ?? false; => combo.Design?.ResetAdvancedDyes ?? false;
public bool ResetTemporarySettings
=> combo.Design?.ResetTemporarySettings ?? false;
} }

View file

@ -92,8 +92,11 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
} }
public bool ForcedRedraw public bool ForcedRedraw
=> false; => _currentDesign?.ForcedRedraw ?? false;
public bool ResetAdvancedDyes public bool ResetAdvancedDyes
=> false; => _currentDesign?.ResetAdvancedDyes ?? false;
public bool ResetTemporarySettings
=> _currentDesign?.ResetTemporarySettings ?? false;
} }

View file

@ -48,4 +48,7 @@ public class RevertDesign : IDesignStandIn
public bool ResetAdvancedDyes public bool ResetAdvancedDyes
=> true; => true;
public bool ResetTemporarySettings
=> true;
} }

View file

@ -93,6 +93,9 @@ public sealed class DesignChanged()
/// <summary> An existing design had changed whether it always resets advanced dyes or not. </summary> /// <summary> An existing design had changed whether it always resets advanced dyes or not. </summary>
ResetAdvancedDyes, ResetAdvancedDyes,
/// <summary> An existing design had changed whether it always resets all prior temporary settings or not. </summary>
ResetTemporarySettings,
/// <summary> An existing design changed whether a specific customization is applied. </summary> /// <summary> An existing design changed whether a specific customization is applied. </summary>
ApplyCustomize, ApplyCustomize,

View file

@ -159,6 +159,7 @@ public class ActorPanel
return; return;
ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Dummy(Vector2.Zero);
var transformationId = _actor.IsCharacter ? _actor.AsCharacter->CharacterData.TransformationId : 0; var transformationId = _actor.IsCharacter ? _actor.AsCharacter->CharacterData.TransformationId : 0;
if (transformationId != 0) if (transformationId != 0)
ImGuiUtil.DrawTextButton($"Currently transformed to Transformation {transformationId}.", ImGuiUtil.DrawTextButton($"Currently transformed to Transformation {transformationId}.",

View file

@ -6,6 +6,7 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
@ -41,7 +42,7 @@ public class DesignDetailTab
public void Draw() public void Draw()
{ {
using var h = ImRaii.CollapsingHeader("Design Details"); using var h = ImUtf8.CollapsingHeaderId("Design Details"u8);
if (!h) if (!h)
return; return;
@ -54,19 +55,19 @@ public class DesignDetailTab
private void DrawDesignInfoTable() private void DrawDesignInfoTable()
{ {
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
using var table = ImRaii.Table("Details", 2); using var table = ImUtf8.Table("Details"u8, 2);
if (!table) if (!table)
return; return;
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Reset Advanced Dyes").X); ImUtf8.TableSetupColumn("Type"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Reset Temporary Settings"u8).X);
ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch); ImUtf8.TableSetupColumn("Data"u8, ImGuiTableColumnFlags.WidthStretch);
ImGuiUtil.DrawFrameColumn("Design Name"); ImUtf8.DrawFrameColumn("Design Name"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var width = new Vector2(ImGui.GetContentRegionAvail().X, 0); var width = new Vector2(ImGui.GetContentRegionAvail().X, 0);
var name = _newName ?? _selector.Selected!.Name; var name = _newName ?? _selector.Selected!.Name;
ImGui.SetNextItemWidth(width.X); ImGui.SetNextItemWidth(width.X);
if (ImGui.InputText("##Name", ref name, 128)) if (ImUtf8.InputText("##Name"u8, ref name))
{ {
_newName = name; _newName = name;
_changeDesign = _selector.Selected; _changeDesign = _selector.Selected;
@ -80,10 +81,10 @@ public class DesignDetailTab
} }
var identifier = _selector.Selected!.Identifier.ToString(); var identifier = _selector.Selected!.Identifier.ToString();
ImGuiUtil.DrawFrameColumn("Unique Identifier"); ImUtf8.DrawFrameColumn("Unique Identifier"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var fileName = _saveService.FileNames.DesignFile(_selector.Selected!); var fileName = _saveService.FileNames.DesignFile(_selector.Selected!);
using (var mono = ImRaii.PushFont(UiBuilder.MonoFont)) using (ImRaii.PushFont(UiBuilder.MonoFont))
{ {
if (ImGui.Button(identifier, width)) if (ImGui.Button(identifier, width))
try try
@ -100,14 +101,14 @@ public class DesignDetailTab
ImGui.SetClipboardText(identifier); ImGui.SetClipboardText(identifier);
} }
ImGuiUtil.HoverTooltip( ImUtf8.HoverTooltip(
$"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard."); $"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard.");
ImGuiUtil.DrawFrameColumn("Full Selector Path"); ImUtf8.DrawFrameColumn("Full Selector Path"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var path = _newPath ?? _selector.SelectedLeaf!.FullName(); var path = _newPath ?? _selector.SelectedLeaf!.FullName();
ImGui.SetNextItemWidth(width.X); ImGui.SetNextItemWidth(width.X);
if (ImGui.InputText("##Path", ref path, 1024)) if (ImUtf8.InputText("##Path"u8, ref path))
{ {
_newPath = path; _newPath = path;
_changeLeaf = _selector.SelectedLeaf!; _changeLeaf = _selector.SelectedLeaf!;
@ -125,32 +126,42 @@ public class DesignDetailTab
Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error); Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error);
} }
ImGuiUtil.DrawFrameColumn("Quick Design Bar"); ImUtf8.DrawFrameColumn("Quick Design Bar"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGui.RadioButton("Display##qdb", _selector.Selected.QuickDesign)) if (ImUtf8.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, true); _manager.SetQuickDesign(_selector.Selected!, true);
var hovered = ImGui.IsItemHovered(); var hovered = ImGui.IsItemHovered();
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.RadioButton("Hide##qdb", !_selector.Selected.QuickDesign)) if (ImUtf8.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign))
_manager.SetQuickDesign(_selector.Selected!, false); _manager.SetQuickDesign(_selector.Selected!, false);
if (hovered || ImGui.IsItemHovered()) if (hovered || ImGui.IsItemHovered())
ImGui.SetTooltip("Display or hide this design in your quick design bar."); {
using var tt = ImUtf8.Tooltip();
ImUtf8.Text("Display or hide this design in your quick design bar."u8);
}
var forceRedraw = _selector.Selected!.ForcedRedraw; var forceRedraw = _selector.Selected!.ForcedRedraw;
ImGuiUtil.DrawFrameColumn("Force Redrawing"); ImUtf8.DrawFrameColumn("Force Redrawing"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGui.Checkbox("##ForceRedraw", ref forceRedraw)) if (ImUtf8.Checkbox("##ForceRedraw"u8, ref forceRedraw))
_manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw);
ImGuiUtil.HoverTooltip("Set this design to always force a redraw when it is applied through any means."); ImUtf8.HoverTooltip("Set this design to always force a redraw when it is applied through any means."u8);
var resetAdvancedDyes = _selector.Selected!.ResetAdvancedDyes; var resetAdvancedDyes = _selector.Selected!.ResetAdvancedDyes;
ImGuiUtil.DrawFrameColumn("Reset Advanced Dyes"); ImUtf8.DrawFrameColumn("Reset Advanced Dyes"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGui.Checkbox("##ResetAdvancedDyes", ref resetAdvancedDyes)) if (ImUtf8.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes))
_manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes); _manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes);
ImGuiUtil.HoverTooltip("Set this design to reset any previously applied advanced dyes when it is applied through any means."); ImUtf8.HoverTooltip("Set this design to reset any previously applied advanced dyes when it is applied through any means."u8);
ImGuiUtil.DrawFrameColumn("Color"); var resetTemporarySettings = _selector.Selected!.ResetTemporarySettings;
ImUtf8.DrawFrameColumn("Reset Temporary Settings"u8);
ImGui.TableNextColumn();
if (ImUtf8.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings))
_manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings);
ImUtf8.HoverTooltip("Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8);
ImUtf8.DrawFrameColumn("Color"u8);
var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color;
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n" if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n"
@ -178,18 +189,18 @@ public class DesignDetailTab
var size = new Vector2(ImGui.GetFrameHeight()); var size = new Vector2(ImGui.GetFrameHeight());
using var font = ImRaii.PushFont(UiBuilder.IconFont); using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor);
ImGuiUtil.HoverTooltip("The color associated with this design does not exist."); ImUtf8.HoverTooltip("The color associated with this design does not exist."u8);
} }
ImGuiUtil.DrawFrameColumn("Creation Date"); ImUtf8.DrawFrameColumn("Creation Date"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0); ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0);
ImGuiUtil.DrawFrameColumn("Last Update Date"); ImUtf8.DrawFrameColumn("Last Update Date"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.DrawTextButton(_selector.Selected!.LastEdit.LocalDateTime.ToString("F"), width, 0); ImGuiUtil.DrawTextButton(_selector.Selected!.LastEdit.LocalDateTime.ToString("F"), width, 0);
ImGuiUtil.DrawFrameColumn("Tags"); ImUtf8.DrawFrameColumn("Tags"u8);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawTags(); DrawTags();
} }
@ -219,18 +230,18 @@ public class DesignDetailTab
var size = new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeightWithSpacing()); var size = new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeightWithSpacing());
if (!_editDescriptionMode) if (!_editDescriptionMode)
{ {
using (var textBox = ImRaii.ListBox("##desc", size)) using (var textBox = ImUtf8.ListBox("##desc"u8, size))
{ {
ImGuiUtil.TextWrapped(desc); ImUtf8.TextWrapped(desc);
} }
if (ImGui.Button("Edit Description")) if (ImUtf8.Button("Edit Description"u8))
_editDescriptionMode = true; _editDescriptionMode = true;
} }
else else
{ {
var edit = _newDescription ?? desc; var edit = _newDescription ?? desc;
if (ImGui.InputTextMultiline("##desc", ref edit, (uint)Math.Max(2000, 4 * edit.Length), size)) if (ImUtf8.InputMultiLine("##desc"u8, ref edit, size))
_newDescription = edit; _newDescription = edit;
if (ImGui.IsItemDeactivatedAfterEdit()) if (ImGui.IsItemDeactivatedAfterEdit())
@ -239,7 +250,7 @@ public class DesignDetailTab
_newDescription = null; _newDescription = null;
} }
if (ImGui.Button("Stop Editing")) if (ImUtf8.Button("Stop Editing"u8))
_editDescriptionMode = false; _editDescriptionMode = false;
} }
} }

View file

@ -423,6 +423,7 @@ public class DesignPanel
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (_selector.Selected == null) if (_selector.Selected == null)
return; return;
ImGui.Dummy(Vector2.Zero);
DrawButtonRow(); DrawButtonRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -438,7 +439,6 @@ public class DesignPanel
private void DrawButtonRow() private void DrawButtonRow()
{ {
ImGui.Dummy(Vector2.Zero);
DrawApplyToSelf(); DrawApplyToSelf();
ImGui.SameLine(); ImGui.SameLine();
DrawApplyToTarget(); DrawApplyToTarget();

View file

@ -88,16 +88,18 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
private void DrawTable() private void DrawTable()
{ {
using var table = ImRaii.Table("Mods", 5, ImGuiTableFlags.RowBg); using var table = ImUtf8.Table("Mods"u8, 7, ImGuiTableFlags.RowBg);
if (!table) if (!table)
return; return;
ImGui.TableSetupColumn("##Buttons", ImGuiTableColumnFlags.WidthFixed, ImUtf8.TableSetupColumn("##Buttons"u8, ImGuiTableColumnFlags.WidthFixed,
ImGui.GetFrameHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 2); ImGui.GetFrameHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 2);
ImGui.TableSetupColumn("Mod Name", ImGuiTableColumnFlags.WidthStretch); ImUtf8.TableSetupColumn("Mod Name"u8, ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("State").X); ImUtf8.TableSetupColumn("Remove"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X);
ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Priority").X); ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X);
ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Applym").X); ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("State"u8).X);
ImUtf8.TableSetupColumn("Priority"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Priority"u8).X);
ImUtf8.TableSetupColumn("##Options"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Applym"u8).X);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
Mod? removedMod = null; Mod? removedMod = null;
@ -183,6 +185,17 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra."); ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra.");
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var remove = settings.Remove;
if (TwoStateCheckbox.Instance.Draw("##Remove"u8, ref remove))
updatedMod = (mod, settings with { Remove = remove });
ImUtf8.HoverTooltip(
"Remove any temporary settings applied by Glamourer instead of applying the configured settings. Only works when using temporary settings, ignored otherwise."u8);
ImGui.TableNextColumn();
var inherit = settings.ForceInherit;
if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref inherit))
updatedMod = (mod, settings with { ForceInherit = inherit });
ImUtf8.HoverTooltip("Force the mod to inherit its settings from inherited collections."u8);
ImGui.TableNextColumn();
var enabled = settings.Enabled; var enabled = settings.Enabled;
if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref enabled)) if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref enabled))
updatedMod = (mod, settings with { Enabled = enabled }); updatedMod = (mod, settings with { Enabled = enabled });

View file

@ -106,6 +106,9 @@ public class SettingsTab(
+ "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n" + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"
+ "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.", + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.",
config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v);
Checkbox("Use Temporary Mod Settings",
"Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down.", config.UseTemporarySettings,
v => config.UseTemporarySettings = v);
ImGui.NewLine(); ImGui.NewLine();
} }
@ -120,6 +123,8 @@ public class SettingsTab(
config.DefaultDesignSettings.ResetAdvancedDyes, v => config.DefaultDesignSettings.ResetAdvancedDyes = v); config.DefaultDesignSettings.ResetAdvancedDyes, v => config.DefaultDesignSettings.ResetAdvancedDyes = v);
Checkbox("Always Force Redraw", "Newly created designs will be configured to force character redraws on application by default.", Checkbox("Always Force Redraw", "Newly created designs will be configured to force character redraws on application by default.",
config.DefaultDesignSettings.AlwaysForceRedrawing, v => config.DefaultDesignSettings.AlwaysForceRedrawing = v); config.DefaultDesignSettings.AlwaysForceRedrawing, v => config.DefaultDesignSettings.AlwaysForceRedrawing = v);
Checkbox("Reset Temporary Settings", "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default.",
config.DefaultDesignSettings.ResetTemporarySettings, v => config.DefaultDesignSettings.ResetTemporarySettings = v);
} }
private void DrawInterfaceSettings() private void DrawInterfaceSettings()

View file

@ -3,12 +3,15 @@ using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop.Penumbra; namespace Glamourer.Interop.Penumbra;
public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects, CollectionOverrideService overrides) public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects, CollectionOverrideService overrides)
: IService : IService
{ {
private readonly HashSet<Guid> _collectionTracker = [];
public void HandleStateApplication(ActorState state, MergedDesign design) public void HandleStateApplication(ActorState state, MergedDesign design)
{ {
if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0) if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0)
@ -22,20 +25,20 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
return; return;
} }
var collections = new HashSet<Guid>(); _collectionTracker.Clear();
foreach (var actor in data.Objects) foreach (var actor in data.Objects)
{ {
var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier); var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier);
if (collection == Guid.Empty) if (collection == Guid.Empty)
continue; continue;
if (!collections.Add(collection)) if (!_collectionTracker.Add(collection))
continue; continue;
var index = ResetOldSettings(collection, actor, design.ResetTemporarySettings);
foreach (var (mod, setting) in design.AssociatedMods) foreach (var (mod, setting) in design.AssociatedMods)
{ {
var message = penumbra.SetMod(mod, setting, collection); var message = penumbra.SetMod(mod, setting, collection, index);
if (message.Length > 0) if (message.Length > 0)
Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}");
else else
@ -45,7 +48,8 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
} }
} }
public (List<string> Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings(IReadOnlyDictionary<Mod, ModSettings> settings, Actor actor) public (List<string> Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings(
IReadOnlyDictionary<Mod, ModSettings> settings, Actor actor, bool resetOther)
{ {
var (collection, name, overridden) = overrides.GetCollection(actor); var (collection, name, overridden) = overrides.GetCollection(actor);
if (collection == Guid.Empty) if (collection == Guid.Empty)
@ -53,9 +57,11 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
var messages = new List<string>(); var messages = new List<string>();
var appliedMods = 0; var appliedMods = 0;
var index = ResetOldSettings(collection, actor, resetOther);
foreach (var (mod, setting) in settings) foreach (var (mod, setting) in settings)
{ {
var message = penumbra.SetMod(mod, setting, collection); var message = penumbra.SetMod(mod, setting, collection, index);
if (message.Length > 0) if (message.Length > 0)
messages.Add($"Error applying mod settings: {message}"); messages.Add($"Error applying mod settings: {message}");
else else
@ -64,4 +70,18 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
return (messages, appliedMods, collection, name, overridden); return (messages, appliedMods, collection, name, overridden);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ObjectIndex? ResetOldSettings(Guid collection, Actor actor, bool resetOther)
{
ObjectIndex? index = actor.Valid ? actor.Index : null;
if (!resetOther)
return index;
if (index == null)
penumbra.RemoveAllTemporarySettings(collection);
else
penumbra.RemoveAllTemporarySettings(index.Value);
return index;
}
} }

View file

@ -21,10 +21,10 @@ public readonly record struct Mod(string Name, string DirectoryName) : IComparab
} }
} }
public readonly record struct ModSettings(Dictionary<string, List<string>> Settings, int Priority, bool Enabled) public readonly record struct ModSettings(Dictionary<string, List<string>> Settings, int Priority, bool Enabled, bool ForceInherit, bool Remove)
{ {
public ModSettings() public ModSettings()
: this(new Dictionary<string, List<string>>(), 0, false) : this(new Dictionary<string, List<string>>(), 0, false, false, false)
{ } { }
public static ModSettings Empty public static ModSettings Empty
@ -33,30 +33,41 @@ public readonly record struct ModSettings(Dictionary<string, List<string>> Setti
public class PenumbraService : IDisposable public class PenumbraService : IDisposable
{ {
public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraBreakingVersion = 5;
public const int RequiredPenumbraFeatureVersion = 0; public const int RequiredPenumbraFeatureVersion = 3;
public const int RequiredPenumbraFeatureVersionTemp = 4;
private readonly IDalamudPluginInterface _pluginInterface; private const int Key = -1610;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly Configuration _config;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber; private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber; private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase; private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase;
private readonly EventSubscriber<nint, Guid, nint> _createdCharacterBase; private readonly EventSubscriber<nint, Guid, nint> _createdCharacterBase;
private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _modSettingChanged; private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _modSettingChanged;
private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier;
private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections;
private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw;
private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo;
private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent;
private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection;
private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods;
private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection;
private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings; private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings;
private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; private global::Penumbra.Api.IpcSubscribers.TryInheritMod? _inheritMod;
private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod;
private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting; private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority;
private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings; private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting;
private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings;
private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings? _setTemporaryModSettings;
private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer? _setTemporaryModSettingsPlayer;
private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings? _removeTemporaryModSettings;
private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer? _removeTemporaryModSettingsPlayer;
private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings;
private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer;
private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage;
private readonly IDisposable _initializedEvent; private readonly IDisposable _initializedEvent;
private readonly IDisposable _disposedEvent; private readonly IDisposable _disposedEvent;
@ -68,10 +79,11 @@ public class PenumbraService : IDisposable
public int CurrentMinor { get; private set; } public int CurrentMinor { get; private set; }
public DateTime AttachTime { get; private set; } public DateTime AttachTime { get; private set; }
public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration config)
{ {
_pluginInterface = pi; _pluginInterface = pi;
_penumbraReloaded = penumbraReloaded; _penumbraReloaded = penumbraReloaded;
_config = config;
_initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach); _initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach);
_disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach); _disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach);
_tooltipSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemTooltip.Subscriber(pi); _tooltipSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemTooltip.Subscriber(pi);
@ -128,7 +140,7 @@ public class PenumbraService : IDisposable
if (ec is not PenumbraApiEc.Success) if (ec is not PenumbraApiEc.Success)
return ModSettings.Empty; return ModSettings.Empty;
return tuple.HasValue ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1) : ModSettings.Empty; return tuple.HasValue ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) : ModSettings.Empty;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -164,7 +176,7 @@ public class PenumbraService : IDisposable
.Select(t => (new Mod(t.Item2, t.Item1), .Select(t => (new Mod(t.Item2, t.Item1),
!t.Item3.Item2.HasValue !t.Item3.Item2.HasValue
? ModSettings.Empty ? ModSettings.Empty
: new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1))) : new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1, false, false)))
.OrderByDescending(p => p.Item2.Enabled) .OrderByDescending(p => p.Item2.Enabled)
.ThenBy(p => p.Item1.Name) .ThenBy(p => p.Item1.Name)
.ThenBy(p => p.Item1.DirectoryName) .ThenBy(p => p.Item1.DirectoryName)
@ -195,7 +207,7 @@ public class PenumbraService : IDisposable
/// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// Try to set all mod settings as desired. Only sets when the mod should be enabled.
/// If it is disabled, ignore all other settings. /// If it is disabled, ignore all other settings.
/// </summary> /// </summary>
public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null) public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null, ObjectIndex? index = null)
{ {
if (!Available) if (!Available)
return "Penumbra is not available."; return "Penumbra is not available.";
@ -204,40 +216,10 @@ public class PenumbraService : IDisposable
try try
{ {
var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id; var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id;
var ec = _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled); if (_config.UseTemporarySettings && _setTemporaryModSettings != null)
switch (ec) SetModTemporary(sb, mod, settings, collection, index);
{ else
case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; SetModPermanent(sb, mod, settings, collection);
case PenumbraApiEc.CollectionMissing: return $"The collection {collection} could not be found.";
}
if (!settings.Enabled)
return string.Empty;
ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority);
Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail.");
foreach (var (setting, list) in settings.Settings)
{
ec = list.Count == 1
? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0])
: _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list);
switch (ec)
{
case PenumbraApiEc.OptionGroupMissing:
sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}.");
break;
case PenumbraApiEc.OptionMissing:
sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}.");
break;
case PenumbraApiEc.Success:
case PenumbraApiEc.NothingChanged:
break;
default:
sb.AppendLine($"Could not apply options in the option group {setting} in mod {mod.Name} for unknown reason {ec}.");
break;
}
}
return sb.ToString(); return sb.ToString();
} }
@ -247,6 +229,103 @@ public class PenumbraService : IDisposable
} }
} }
public void RemoveAllTemporarySettings(Guid collection)
=> _removeAllTemporaryModSettings?.Invoke(collection, Key);
public void RemoveAllTemporarySettings(ObjectIndex index)
=> _removeAllTemporaryModSettingsPlayer?.Invoke(index.Index, Key);
public void ClearAllTemporarySettings()
{
if (!Available || _removeAllTemporaryModSettings == null)
return;
var collections = _collections!.Invoke();
foreach (var collection in collections)
RemoveAllTemporarySettings(collection.Key);
}
private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index)
{
var ex = settings.Remove
? index.HasValue
? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, Key)
: _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, Key)
: index.HasValue
? _setTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, settings.ForceInherit, settings.Enabled,
settings.Priority,
settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<string>)kvp.Value), "Glamourer", Key)
: _setTemporaryModSettings!.Invoke(collection, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority,
settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<string>)kvp.Value), "Glamourer", Key);
switch (ex)
{
case PenumbraApiEc.InvalidArgument:
sb.Append($"No actor with index {index!.Value.Index} could be identified.");
return;
case PenumbraApiEc.ModMissing:
sb.Append($"The mod {mod.Name} [{mod.DirectoryName}] could not be found.");
return;
case PenumbraApiEc.CollectionMissing:
sb.Append($"The collection {collection} could not be found.");
return;
case PenumbraApiEc.TemporarySettingImpossible:
sb.Append($"The collection {collection} can not have settings.");
return;
case PenumbraApiEc.TemporarySettingDisallowed:
sb.Append($"The mod {mod.Name} [{mod.DirectoryName}] already has temporary settings with a different key in {collection}.");
return;
case PenumbraApiEc.OptionGroupMissing:
case PenumbraApiEc.OptionMissing:
sb.Append($"The provided settings for {mod.Name} [{mod.DirectoryName}] did not correspond to its actual options.");
return;
}
}
private void SetModPermanent(StringBuilder sb, Mod mod, ModSettings settings, Guid collection)
{
var ec = settings.ForceInherit
? _inheritMod!.Invoke(collection, mod.DirectoryName, true)
: _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled);
switch (ec)
{
case PenumbraApiEc.ModMissing:
sb.Append($"The mod {mod.Name} [{mod.DirectoryName}] could not be found.");
return;
case PenumbraApiEc.CollectionMissing:
sb.Append($"The collection {collection} could not be found.");
return;
}
if (settings.ForceInherit || !settings.Enabled)
return;
ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority);
Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail.");
foreach (var (setting, list) in settings.Settings)
{
ec = list.Count == 1
? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0])
: _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list);
switch (ec)
{
case PenumbraApiEc.OptionGroupMissing:
sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}.");
break;
case PenumbraApiEc.OptionMissing:
sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}.");
break;
case PenumbraApiEc.Success:
case PenumbraApiEc.NothingChanged:
break;
default:
sb.AppendLine($"Could not apply options in the option group {setting} in mod {mod.Name} for unknown reason {ec}.");
break;
}
}
}
/// <summary> Obtain the name of the collection currently assigned to the player. </summary> /// <summary> Obtain the name of the collection currently assigned to the player. </summary>
public Guid GetCurrentPlayerCollection() public Guid GetCurrentPlayerCollection()
{ {
@ -347,12 +426,24 @@ public class PenumbraService : IDisposable
_getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface);
_currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface);
_getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface);
_inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface);
_setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface);
_setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface);
_setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface);
_setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface);
_openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface);
Available = true; if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp)
{
_setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface);
_setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface);
_removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface);
_removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface);
_removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface);
_removeAllTemporaryModSettingsPlayer =
new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface);
}
Available = true;
_penumbraReloaded.Invoke(); _penumbraReloaded.Invoke();
Glamourer.Log.Debug("Glamourer attached to Penumbra."); Glamourer.Log.Debug("Glamourer attached to Penumbra.");
} }
@ -373,27 +464,35 @@ public class PenumbraService : IDisposable
_modSettingChanged.Disable(); _modSettingChanged.Disable();
if (Available) if (Available)
{ {
_collectionByIdentifier = null; _collectionByIdentifier = null;
_collections = null; _collections = null;
_redraw = null; _redraw = null;
_drawObjectInfo = null; _drawObjectInfo = null;
_cutsceneParent = null; _cutsceneParent = null;
_objectCollection = null; _objectCollection = null;
_getMods = null; _getMods = null;
_currentCollection = null; _currentCollection = null;
_getCurrentSettings = null; _getCurrentSettings = null;
_setMod = null; _inheritMod = null;
_setModPriority = null; _setMod = null;
_setModSetting = null; _setModPriority = null;
_setModSettings = null; _setModSetting = null;
_openModPage = null; _setModSettings = null;
Available = false; _openModPage = null;
_setTemporaryModSettings = null;
_setTemporaryModSettingsPlayer = null;
_removeTemporaryModSettings = null;
_removeTemporaryModSettingsPlayer = null;
_removeAllTemporaryModSettings = null;
_removeAllTemporaryModSettingsPlayer = null;
Available = false;
Glamourer.Log.Debug("Glamourer detached from Penumbra."); Glamourer.Log.Debug("Glamourer detached from Penumbra.");
} }
} }
public void Dispose() public void Dispose()
{ {
ClearAllTemporarySettings();
Unattach(); Unattach();
_tooltipSubscriber.Dispose(); _tooltipSubscriber.Dispose();
_clickSubscriber.Dispose(); _clickSubscriber.Dispose();

View file

@ -691,7 +691,7 @@ public class CommandService : IDisposable, IApiService
if (!applyMods || design is not Design d) if (!applyMods || design is not Design d)
return; return;
var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, d.ResetTemporarySettings);
foreach (var message in messages) foreach (var message in messages)
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");

@ -1 +1 @@
Subproject commit 882b778e78bb0806dd7d38e8b3670ff138a84a31 Subproject commit de0f281fbf9d8d9d3aa8463a28025d54877cde8d