Current State.

This commit is contained in:
Ottermandias 2024-12-27 17:51:17 +01:00
parent 98a89bb2b4
commit 282189ef6d
9 changed files with 115 additions and 56 deletions

@ -1 +1 @@
Subproject commit 97e9f427406f82a59ddef764b44ecea654a51623 Subproject commit fdda2054c26a30111ac55984ed6efde7f7214b68

View file

@ -333,6 +333,9 @@ public class CollectionCacheManager : IDisposable, IService
cache.ReloadMod(mod, true); cache.ReloadMod(mod, true);
break; break;
case ModSettingChange.TemporarySetting:
cache.ReloadMod(mod!, true);
break;
case ModSettingChange.MultiInheritance: case ModSettingChange.MultiInheritance:
case ModSettingChange.MultiEnableState: case ModSettingChange.MultiEnableState:
FullRecalculation(collection); FullRecalculation(collection);

View file

@ -88,7 +88,7 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
/// <summary> /// <summary>
/// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary. /// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary.
/// /// If the mod is currently inherited, stop the inheritance. /// If the mod is currently inherited, stop the inheritance.
/// </summary> /// </summary>
public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, Setting newValue) public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, Setting newValue)
{ {
@ -103,6 +103,18 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
return true; return true;
} }
public bool SetTemporarySettings(ModCollection collection, Mod mod, TemporaryModSettings? settings, int key = 0)
{
key = settings?.Lock ?? key;
var old = collection.GetTempSettings(mod.Index);
if (old != null && old.Lock != 0 && old.Lock != key)
return false;
collection.Settings.SetTemporary(mod.Index, settings);
InvokeChange(collection, ModSettingChange.TemporarySetting, mod, Setting.Indefinite, 0);
return true;
}
/// <summary> Copy the settings of an existing (sourceMod != null) or stored (sourceName) mod to another mod, if they exist. </summary> /// <summary> Copy the settings of an existing (sourceMod != null) or stored (sourceName) mod to another mod, if they exist. </summary>
public bool CopyModSettings(ModCollection collection, Mod? sourceMod, string sourceName, Mod? targetMod, string targetName) public bool CopyModSettings(ModCollection collection, Mod? sourceMod, string sourceName, Mod? targetMod, string targetName)
{ {
@ -168,7 +180,7 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
if (inherit == (settings == null)) if (inherit == (settings == null))
return false; return false;
ModSettings? settings1 = inherit ? null : collection.GetInheritedSettings(mod.Index).Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod); var settings1 = inherit ? null : collection.GetInheritedSettings(mod.Index).Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
collection.Settings.Set(mod.Index, settings1); collection.Settings.Set(mod.Index, settings1);
return true; return true;
} }
@ -179,7 +191,8 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
{ {
saveService.QueueSave(new ModCollectionSave(modStorage, changedCollection)); saveService.QueueSave(new ModCollectionSave(modStorage, changedCollection));
communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false); communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false);
RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx); if (type is not ModSettingChange.TemporarySetting)
RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx);
} }
/// <summary> Trigger changes in all inherited collections. </summary> /// <summary> Trigger changes in all inherited collections. </summary>

View file

@ -36,17 +36,11 @@ public class ModSelection : EventWrapper<Mod?, Mod?, ModSelection.Priority>
_communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModSelection); _communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModSelection);
} }
public ModSettings Settings { get; private set; } = ModSettings.Empty; public ModSettings Settings { get; private set; } = ModSettings.Empty;
public ModCollection Collection { get; private set; } = ModCollection.Empty; public ModCollection Collection { get; private set; } = ModCollection.Empty;
public Mod? Mod { get; private set; } public Mod? Mod { get; private set; }
public ModSettings? OwnSettings { get; private set; } public ModSettings? OwnSettings { get; private set; }
public TemporaryModSettings? TemporarySettings { get; private set; }
public bool IsTemporary
=> OwnSettings != Settings;
public TemporaryModSettings? AsTemporarySettings
=> Settings as TemporaryModSettings;
public void SelectMod(Mod? mod) public void SelectMod(Mod? mod)
{ {
@ -98,6 +92,7 @@ public class ModSelection : EventWrapper<Mod?, Mod?, ModSelection.Priority>
{ {
(var settings, Collection) = _collections.Current.GetActualSettings(Mod.Index); (var settings, Collection) = _collections.Current.GetActualSettings(Mod.Index);
OwnSettings = _collections.Current.GetOwnSettings(Mod.Index); OwnSettings = _collections.Current.GetOwnSettings(Mod.Index);
TemporarySettings = _collections.Current.GetTempSettings(Mod.Index);
Settings = settings ?? ModSettings.Empty; Settings = settings ?? ModSettings.Empty;
} }
} }

View file

@ -12,6 +12,7 @@ namespace Penumbra.Mods.Settings;
public class ModSettings public class ModSettings
{ {
public static readonly ModSettings Empty = new(); public static readonly ModSettings Empty = new();
public SettingList Settings { get; internal init; } = []; public SettingList Settings { get; internal init; } = [];
public ModPriority Priority { get; set; } public ModPriority Priority { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }

View file

@ -5,4 +5,21 @@ public sealed class TemporaryModSettings : ModSettings
public string Source = string.Empty; public string Source = string.Empty;
public int Lock = 0; public int Lock = 0;
public bool ForceInherit; public bool ForceInherit;
// Create default settings for a given mod.
public static TemporaryModSettings DefaultSettings(Mod mod, string source, int key = 0)
=> new()
{
Enabled = false,
Source = source,
Lock = key,
Priority = ModPriority.Default,
Settings = SettingList.Default(mod),
};
}
public static class ModSettingsExtensions
{
public static bool IsTemporary(this ModSettings? settings)
=> settings is TemporaryModSettings;
} }

View file

@ -742,7 +742,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService
private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool inherited) private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool inherited)
{ {
if (collection != _collectionManager.Active.Current || mod != _mod) if (collection != _collectionManager.Active.Current || mod != _mod || type is ModSettingChange.TemporarySetting)
return; return;
_swapData.LoadMod(_mod, _modSettings); _swapData.LoadMod(_mod, _modSettings);

View file

@ -1,8 +1,9 @@
using ImGuiNET;
using OtterGui.Custom; using OtterGui.Custom;
namespace Penumbra.UI.Classes; namespace Penumbra.UI.Classes;
public enum ColorId public enum ColorId : short
{ {
EnabledMod, EnabledMod,
DisabledMod, DisabledMod,
@ -10,6 +11,7 @@ public enum ColorId
InheritedMod, InheritedMod,
InheritedDisabledMod, InheritedDisabledMod,
NewMod, NewMod,
NewModTint,
ConflictingMod, ConflictingMod,
HandledConflictMod, HandledConflictMod,
FolderExpanded, FolderExpanded,
@ -31,10 +33,8 @@ public enum ColorId
ResTreeNonNetworked, ResTreeNonNetworked,
PredefinedTagAdd, PredefinedTagAdd,
PredefinedTagRemove, PredefinedTagRemove,
TemporaryEnabledMod, TemporaryModSettingsTint,
TemporaryDisabledMod, NoTint,
TemporaryInheritedMod,
TemporaryInheritedDisabledMod,
} }
public static class Colors public static class Colors
@ -52,6 +52,18 @@ public static class Colors
public const uint ReniColorHovered = CustomGui.ReniColorHovered; public const uint ReniColorHovered = CustomGui.ReniColorHovered;
public const uint ReniColorActive = CustomGui.ReniColorActive; public const uint ReniColorActive = CustomGui.ReniColorActive;
public static uint Tinted(this ColorId color, ColorId tint)
{
var tintValue = ImGui.ColorConvertU32ToFloat4(tint.Value());
var value = ImGui.ColorConvertU32ToFloat4(color.Value());
var negAlpha = 1 - tintValue.W;
var newAlpha = negAlpha * value.W + tintValue.W;
var newR = (negAlpha * value.W * value.X + tintValue.W * tintValue.X) / newAlpha;
var newG = (negAlpha * value.W * value.Y + tintValue.W * tintValue.Y) / newAlpha;
var newB = (negAlpha * value.W * value.Z + tintValue.W * tintValue.Z) / newAlpha;
return ImGui.ColorConvertFloat4ToU32(new Vector4(newR, newG, newB, newAlpha));
}
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color) public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
=> color switch => color switch
{ {
@ -83,10 +95,9 @@ public static class Colors
ColorId.ResTreeNonNetworked => ( 0xFFC0C0FF, "On-Screen: Non-Players (Local)", "Non-player entities handled locally, in the On-Screen tab." ), ColorId.ResTreeNonNetworked => ( 0xFFC0C0FF, "On-Screen: Non-Players (Local)", "Non-player entities handled locally, in the On-Screen tab." ),
ColorId.PredefinedTagAdd => ( 0xFF44AA44, "Predefined Tags: Add Tag", "A predefined tag that is not present on the current mod and can be added." ), ColorId.PredefinedTagAdd => ( 0xFF44AA44, "Predefined Tags: Add Tag", "A predefined tag that is not present on the current mod and can be added." ),
ColorId.PredefinedTagRemove => ( 0xFF2222AA, "Predefined Tags: Remove Tag", "A predefined tag that is already present on the current mod and can be removed." ), ColorId.PredefinedTagRemove => ( 0xFF2222AA, "Predefined Tags: Remove Tag", "A predefined tag that is already present on the current mod and can be removed." ),
ColorId.TemporaryEnabledMod => ( 0xFFFFC0A0, "Mod Enabled By Temporary Settings", "A mod that is enabled by temporary settings in the currently selected collection." ), ColorId.TemporaryModSettingsTint => ( 0x30FF0000, "Mod with Temporary Settings", "A mod that has temporary settings. This color is used as a tint for the regular state colors." ),
ColorId.TemporaryDisabledMod => ( 0xFFB08070, "Mod Disabled By Temporary Settings", "A mod that is disabled by temporary settings in the currently selected collection." ), ColorId.NewModTint => ( 0x8000FF00, "New Mod Tint", "A mod that was newly imported or created during this session and has not been enabled yet. This color is used as a tint for the regular state colors."),
ColorId.TemporaryInheritedMod => ( 0xFFE8FFB0, "Mod Enabled By Temporary Inheritance", "A mod that is forced to inherit by temporary settings in the currently selected collection." ), ColorId.NoTint => ( 0x00000000, "No Tint", "The default tint for all mods."),
ColorId.TemporaryInheritedDisabledMod => ( 0xFF90A080, "Mod Disabled By Temporary Inheritance", "A mod that is forced to inherit by temporary settings in the currently selected collection." ),
_ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ), _ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ),
// @formatter:on // @formatter:on
}; };

View file

@ -62,6 +62,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
SubscribeRightClickFolder(f => SetQuickMove(f, 1, _config.QuickMoveFolder2, s => { _config.QuickMoveFolder2 = s; _config.Save(); }), 120); SubscribeRightClickFolder(f => SetQuickMove(f, 1, _config.QuickMoveFolder2, s => { _config.QuickMoveFolder2 = s; _config.Save(); }), 120);
SubscribeRightClickFolder(f => SetQuickMove(f, 2, _config.QuickMoveFolder3, s => { _config.QuickMoveFolder3 = s; _config.Save(); }), 130); SubscribeRightClickFolder(f => SetQuickMove(f, 2, _config.QuickMoveFolder3, s => { _config.QuickMoveFolder3 = s; _config.Save(); }), 130);
SubscribeRightClickLeaf(ToggleLeafFavorite); SubscribeRightClickLeaf(ToggleLeafFavorite);
SubscribeRightClickLeaf(RemoveTemporarySettings);
SubscribeRightClickLeaf(DisableTemporarily);
SubscribeRightClickLeaf(l => QuickMove(l, _config.QuickMoveFolder1, _config.QuickMoveFolder2, _config.QuickMoveFolder3)); SubscribeRightClickLeaf(l => QuickMove(l, _config.QuickMoveFolder1, _config.QuickMoveFolder2, _config.QuickMoveFolder3));
SubscribeRightClickMain(ClearDefaultImportFolder, 100); SubscribeRightClickMain(ClearDefaultImportFolder, 100);
SubscribeRightClickMain(() => ClearQuickMove(0, _config.QuickMoveFolder1, () => {_config.QuickMoveFolder1 = string.Empty; _config.Save();}), 110); SubscribeRightClickMain(() => ClearQuickMove(0, _config.QuickMoveFolder1, () => {_config.QuickMoveFolder1 = string.Empty; _config.Save();}), 110);
@ -194,7 +196,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
protected override void DrawLeafName(FileSystem<Mod>.Leaf leaf, in ModState state, bool selected) protected override void DrawLeafName(FileSystem<Mod>.Leaf leaf, in ModState state, bool selected)
{ {
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags; var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Value()) using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Tinted(state.Tint))
.Push(ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite); .Push(ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite);
using var id = ImRaii.PushId(leaf.Value.Index); using var id = ImRaii.PushId(leaf.Value.Index);
ImRaii.TreeNode(leaf.Value.Name, flags).Dispose(); ImRaii.TreeNode(leaf.Value.Name, flags).Dispose();
@ -264,6 +266,23 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
_modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite); _modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite);
} }
private void RemoveTemporarySettings(FileSystem<Mod>.Leaf mod)
{
var tempSettings = _collectionManager.Active.Current.GetTempSettings(mod.Value.Index);
if (tempSettings is { Lock: 0 })
if (ImUtf8.MenuItem("Remove Temporary Settings"))
_collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, null);
}
private void DisableTemporarily(FileSystem<Mod>.Leaf mod)
{
var tempSettings = _collectionManager.Active.Current.GetTempSettings(mod.Value.Index);
if (tempSettings == null || tempSettings.Lock == 0)
if (ImUtf8.MenuItem("Disable Temporarily"))
_collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value,
TemporaryModSettings.DefaultSettings(mod.Value, "User Context-Menu"));
}
private void SetDefaultImportFolder(ModFileSystem.Folder folder) private void SetDefaultImportFolder(ModFileSystem.Folder folder)
{ {
if (!ImGui.MenuItem("Set As Default Import Folder")) if (!ImGui.MenuItem("Set As Default Import Folder"))
@ -392,8 +411,6 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
ImGuiUtil.BulletTextColored(ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection."); ImGuiUtil.BulletTextColored(ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection.");
ImGuiUtil.BulletTextColored(ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection."); ImGuiUtil.BulletTextColored(ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection.");
ImGuiUtil.BulletTextColored(ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections."); ImGuiUtil.BulletTextColored(ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections.");
ImGuiUtil.BulletTextColored(ColorId.NewMod.Value(),
"newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded.");
ImGuiUtil.BulletTextColored(ColorId.HandledConflictMod.Value(), ImGuiUtil.BulletTextColored(ColorId.HandledConflictMod.Value(),
"enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."); "enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved).");
ImGuiUtil.BulletTextColored(ColorId.ConflictingMod.Value(), ImGuiUtil.BulletTextColored(ColorId.ConflictingMod.Value(),
@ -501,6 +518,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
public struct ModState public struct ModState
{ {
public ColorId Color; public ColorId Color;
public ColorId Tint;
public ModPriority Priority; public ModPriority Priority;
} }
@ -571,31 +589,31 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
=> !_filter.IsVisible(leaf); => !_filter.IsVisible(leaf);
/// <summary> Only get the text color for a mod if no filters are set. </summary> /// <summary> Only get the text color for a mod if no filters are set. </summary>
private ColorId GetTextColor(Mod mod, ModSettings? settings, ModCollection collection) private (ColorId Color, ColorId Tint) GetTextColor(Mod mod, ModSettings? settings, ModCollection collection)
{ {
if (_modManager.IsNew(mod)) var tint = settings.IsTemporary()
return ColorId.NewMod; ? ColorId.TemporaryModSettingsTint
: _modManager.IsNew(mod)
? ColorId.NewModTint
: ColorId.NoTint;
if (settings.IsTemporary())
tint = ColorId.TemporaryModSettingsTint;
if (settings == null) if (settings == null)
return ColorId.UndefinedMod; return (ColorId.UndefinedMod, tint);
if (!settings.Enabled) if (!settings.Enabled)
return collection != _collectionManager.Active.Current return (collection != _collectionManager.Active.Current
? ColorId.InheritedDisabledMod ? ColorId.InheritedDisabledMod
: settings is TemporaryModSettings : ColorId.DisabledMod, tint);
? ColorId.TemporaryDisabledMod
: ColorId.DisabledMod;
if (settings is TemporaryModSettings)
return ColorId.TemporaryEnabledMod;
var conflicts = _collectionManager.Active.Current.Conflicts(mod); var conflicts = _collectionManager.Active.Current.Conflicts(mod);
if (conflicts.Count == 0) if (conflicts.Count == 0)
return collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod; return (collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod, tint);
return conflicts.Any(c => !c.Solved) return (conflicts.Any(c => !c.Solved)
? ColorId.ConflictingMod ? ColorId.ConflictingMod
: ColorId.HandledConflictMod; : ColorId.HandledConflictMod, tint);
} }
private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state) private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state)
@ -627,6 +645,15 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
return true; return true;
} }
// isNew color takes precedence before other colors.
if (settings.IsTemporary())
state.Tint = ColorId.TemporaryModSettingsTint;
else if (isNew)
state.Tint = ColorId.NewModTint;
else
state.Tint = ColorId.NoTint;
// Handle settings. // Handle settings.
if (settings == null) if (settings == null)
{ {
@ -640,9 +667,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
{ {
state.Color = collection != _collectionManager.Active.Current state.Color = collection != _collectionManager.Active.Current
? ColorId.InheritedDisabledMod ? ColorId.InheritedDisabledMod
: settings is TemporaryModSettings : ColorId.DisabledMod;
? ColorId.TemporaryDisabledMod
: ColorId.DisabledMod;
if (!_stateFilter.HasFlag(ModFilter.Disabled) if (!_stateFilter.HasFlag(ModFilter.Disabled)
|| !_stateFilter.HasFlag(ModFilter.NoConflict)) || !_stateFilter.HasFlag(ModFilter.NoConflict))
return true; return true;
@ -652,10 +677,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
if (!_stateFilter.HasFlag(ModFilter.Enabled)) if (!_stateFilter.HasFlag(ModFilter.Enabled))
return true; return true;
if (settings is TemporaryModSettings) // Conflicts can only be relevant if the mod is enabled.
state.Color = ColorId.TemporaryEnabledMod;
// Conflicts can only be relevant if the mod is enabled.
var conflicts = _collectionManager.Active.Current.Conflicts(mod); var conflicts = _collectionManager.Active.Current.Conflicts(mod);
if (conflicts.Count > 0) if (conflicts.Count > 0)
{ {
@ -664,14 +686,14 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
if (!_stateFilter.HasFlag(ModFilter.UnsolvedConflict)) if (!_stateFilter.HasFlag(ModFilter.UnsolvedConflict))
return true; return true;
state.Color = settings is TemporaryModSettings ? ColorId.TemporaryEnabledMod : ColorId.ConflictingMod; state.Color = ColorId.ConflictingMod;
} }
else else
{ {
if (!_stateFilter.HasFlag(ModFilter.SolvedConflict)) if (!_stateFilter.HasFlag(ModFilter.SolvedConflict))
return true; return true;
state.Color = settings is TemporaryModSettings ? ColorId.TemporaryEnabledMod : ColorId.HandledConflictMod; state.Color = ColorId.HandledConflictMod;
} }
} }
else if (!_stateFilter.HasFlag(ModFilter.NoConflict)) else if (!_stateFilter.HasFlag(ModFilter.NoConflict))
@ -680,9 +702,6 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
} }
} }
// isNew color takes precedence before other colors.
if (isNew)
state.Color = ColorId.NewMod;
return false; return false;
} }
@ -704,7 +723,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
if (_stateFilter != ModFilterExtensions.UnfilteredStateMods) if (_stateFilter != ModFilterExtensions.UnfilteredStateMods)
return CheckStateFilters(mod, settings, collection, ref state); return CheckStateFilters(mod, settings, collection, ref state);
state.Color = GetTextColor(mod, settings, collection); (state.Color, state.Tint) = GetTextColor(mod, settings, collection);
return false; return false;
} }