Merge remote-tracking branch 'origin/2.0.3' into i18n

This commit is contained in:
Tsubasahane
2026-01-06 10:03:26 +08:00
87 changed files with 17359 additions and 2753 deletions

View File

@@ -5,6 +5,7 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using Lifestream.Enums;
using LightlessSync.API.Data;
using LightlessSync.API.Data.Comparer;
using LightlessSync.API.Data.Enum;
@@ -14,6 +15,7 @@ using LightlessSync.Interop.Ipc;
using LightlessSync.LightlessConfiguration;
using LightlessSync.LightlessConfiguration.Configurations;
using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.PlayerData.Factories;
using LightlessSync.PlayerData.Handlers;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services;
@@ -40,6 +42,7 @@ using System.Globalization;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
@@ -51,7 +54,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly CacheMonitor _cacheMonitor;
private readonly LightlessConfigService _configService;
private readonly UiThemeConfigService _themeConfigService;
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
private readonly ConcurrentDictionary<GameObjectHandler, IReadOnlyDictionary<string, FileDownloadStatus>> _currentDownloads = new();
private readonly DalamudUtilService _dalamudUtilService;
private readonly HttpClient _httpClient;
private readonly FileCacheManager _fileCacheManager;
@@ -69,6 +72,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly UiSharedService _uiShared;
private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress;
private readonly NameplateService _nameplateService;
private readonly AnimatedHeader _animatedHeader = new();
private (int, int, FileCacheEntity) _currentProgress;
private bool _deleteAccountPopupModalShown = false;
private bool _deleteFilesPopupModalShown = false;
@@ -105,8 +110,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
};
private readonly UiSharedService.TabOption<TransferSettingsTab>[] _transferTabOptions = new UiSharedService.TabOption<TransferSettingsTab>[2];
private readonly List<UiSharedService.TabOption<ServerSettingsTab>> _serverTabOptions = new(4);
private readonly string[] _generalTreeNavOrder = new[]
{
private readonly string[] _generalTreeNavOrder =
[
"Import & Export",
"Popup & Auto Fill",
"Behavior",
@@ -116,7 +121,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
"Colors",
"Server Info Bar",
"Nameplate",
};
"Animation & Bones"
];
private static readonly HashSet<string> _generalNavSeparatorAfter = new(StringComparer.Ordinal)
{
"Popup & Auto Fill",
@@ -205,7 +211,10 @@ public class SettingsUi : WindowMediatorSubscriberBase
_nameplateService = nameplateService;
_actorObjectService = actorObjectService;
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
_animatedHeader.Height = 120f;
_animatedHeader.EnableBottomGradient = true;
_animatedHeader.GradientHeight = 250f;
_animatedHeader.EnableParticles = _configService.Current.EnableParticleEffects;
WindowBuilder.For(this)
.AllowPinning(true)
.AllowClickthrough(false)
@@ -241,6 +250,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
public override void OnClose()
{
_animatedHeader.ClearParticles();
_uiShared.EditTrackerPosition = false;
_uidToAddForIgnore = string.Empty;
_secretKeysConversionCts = _secretKeysConversionCts.CancelRecreate();
@@ -255,8 +265,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
protected override void DrawInternal()
{
_animatedHeader.Draw(ImGui.GetContentRegionAvail().X, (_, _) => { });
_ = _uiShared.DrawOtherPluginState();
DrawSettingsContent();
}
private static Vector3 PackedColorToVector3(uint color)
@@ -574,6 +584,94 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
}
private void DrawTriangleDecimationCounters()
{
HashSet<Pair> trackedPairs = new();
var snapshot = _pairUiService.GetSnapshot();
foreach (var pair in snapshot.DirectPairs)
{
trackedPairs.Add(pair);
}
foreach (var group in snapshot.GroupPairs.Values)
{
foreach (var pair in group)
{
trackedPairs.Add(pair);
}
}
long totalOriginalTris = 0;
long totalEffectiveTris = 0;
var hasData = false;
foreach (var pair in trackedPairs)
{
if (!pair.IsVisible)
continue;
var original = pair.LastAppliedDataTris;
var effective = pair.LastAppliedApproximateEffectiveTris;
if (original >= 0)
{
hasData = true;
totalOriginalTris += original;
}
if (effective >= 0)
{
hasData = true;
totalEffectiveTris += effective;
}
}
if (!hasData)
{
ImGui.TextDisabled("Triangle usage has not been calculated yet.");
return;
}
var savedTris = Math.Max(0L, totalOriginalTris - totalEffectiveTris);
var originalText = FormatTriangleCount(totalOriginalTris);
var effectiveText = FormatTriangleCount(totalEffectiveTris);
var savedText = FormatTriangleCount(savedTris);
ImGui.TextUnformatted($"Total triangle usage (original): {originalText}");
ImGui.TextUnformatted($"Total triangle usage (effective): {effectiveText}");
if (savedTris > 0)
{
UiSharedService.ColorText($"Triangles saved by decimation: {savedText}", UIColors.Get("LightlessGreen"));
}
else
{
ImGui.TextUnformatted($"Triangles saved by decimation: {savedText}");
}
static string FormatTriangleCount(long triangleCount)
{
if (triangleCount < 0)
{
return "n/a";
}
if (triangleCount >= 1_000_000)
{
return FormattableString.Invariant($"{triangleCount / 1_000_000d:0.#}m tris");
}
if (triangleCount >= 1_000)
{
return FormattableString.Invariant($"{triangleCount / 1_000d:0.#}k tris");
}
return $"{triangleCount} tris";
}
}
private void DrawThemeVectorRow(MainStyle.StyleVector2Option option)
{
ImGui.TableNextRow();
@@ -863,10 +961,11 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.DrawHelpText(
$"The download window will show the current progress of outstanding downloads.{Environment.NewLine}{Environment.NewLine}" +
$"What do W/Q/P/D stand for?{Environment.NewLine}W = Waiting for Slot (see Maximum Parallel Downloads){Environment.NewLine}" +
$"What do W/Q/P/D/C stand for?{Environment.NewLine}W = Waiting for Slot (see Maximum Parallel Downloads){Environment.NewLine}" +
$"Q = Queued on Server, waiting for queue ready signal{Environment.NewLine}" +
$"P = Processing download (aka downloading){Environment.NewLine}" +
$"D = Decompressing download");
$"D = Decompressing download{Environment.NewLine}" +
$"C = Completed download");
if (!_configService.Current.ShowTransferWindow) ImGui.BeginDisabled();
ImGui.Indent();
@@ -1141,7 +1240,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
private async Task<List<string>?> RunSpeedTest(List<string> servers, CancellationToken token)
{
List<string> speedTestResults = new();
List<string> speedTestResults = [];
foreach (var server in servers)
{
HttpResponseMessage? result = null;
@@ -1243,7 +1342,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.TreePop();
}
#endif
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard"))
{
if (LastCreatedCharacterData != null)
@@ -1259,6 +1357,39 @@ public class SettingsUi : WindowMediatorSubscriberBase
UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server.");
if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to Limsa [LIFESTREAM TEST]") && _ipcManager.Lifestream.APIAvailable)
{
_ipcManager.Lifestream.ExecuteLifestreamCommand("limsa");
}
if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to JoyHouse [LIFESTREAM TEST]") && _ipcManager.Lifestream.APIAvailable)
{
var twintania = _dalamudUtilService.WorldData.Value
.FirstOrDefault(kvp => kvp.Value.Equals("Twintania", StringComparison.OrdinalIgnoreCase));
int ward = 29;
int plot = 7;
AddressBookEntryTuple addressEntry = (
Name: "",
World: (int)twintania.Key,
City: (int)ResidentialAetheryteKind.Kugane,
Ward: ward,
PropertyType: 0,
Plot: plot,
Apartment: 1,
ApartmentSubdivision: false,
AliasEnabled: false,
Alias: ""
);
_logger.LogInformation("going to: {address}", addressEntry);
_ipcManager.Lifestream.GoToHousingAddress(addressEntry);
}
#endif
_uiShared.DrawCombo("Log Level", Enum.GetValues<LogLevel>(), (l) => l.ToString(), (l) =>
{
_configService.Current.LogLevel = l;
@@ -1494,6 +1625,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
DrawPairPropertyRow("Approx. VRAM", FormatBytes(pair.LastAppliedApproximateVRAMBytes));
DrawPairPropertyRow("Effective VRAM", FormatBytes(pair.LastAppliedApproximateEffectiveVRAMBytes));
DrawPairPropertyRow("Last Triangles", pair.LastAppliedDataTris < 0 ? "n/a" : pair.LastAppliedDataTris.ToString(CultureInfo.InvariantCulture));
DrawPairPropertyRow("Effective Triangles", pair.LastAppliedApproximateEffectiveTris < 0 ? "n/a" : pair.LastAppliedApproximateEffectiveTris.ToString(CultureInfo.InvariantCulture));
ImGui.EndTable();
}
@@ -1925,14 +2057,25 @@ public class SettingsUi : WindowMediatorSubscriberBase
{
using (ImRaii.PushIndent(20f))
{
if (_validationTask.IsCompleted)
if (_validationTask.IsCompletedSuccessfully)
{
UiSharedService.TextWrapped(
$"The storage validation has completed and removed {_validationTask.Result.Count} invalid files from storage.");
}
else if (_validationTask.IsCanceled)
{
UiSharedService.ColorTextWrapped(
"Storage validation was cancelled.",
UIColors.Get("LightlessYellow"));
}
else if (_validationTask.IsFaulted)
{
UiSharedService.ColorTextWrapped(
"Storage validation failed with an error.",
UIColors.Get("DimRed"));
}
else
{
UiSharedService.TextWrapped(
$"Storage validation is running: {_currentProgress.Item1}/{_currentProgress.Item2}");
if (_currentProgress.Item3 != null)
@@ -2089,7 +2232,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Separator();
var openPopupOnAddition = _configService.Current.OpenPopupOnAdd;
using (var popupTree = BeginGeneralTree("Popup & Auto Fill", UIColors.Get("LightlessPurple")))
{
if (popupTree.Visible)
@@ -2146,11 +2289,20 @@ public class SettingsUi : WindowMediatorSubscriberBase
var groupInVisible = _configService.Current.ShowSyncshellUsersInVisible;
var syncshellOfflineSeparate = _configService.Current.ShowSyncshellOfflineUsersSeparately;
var greenVisiblePair = _configService.Current.ShowVisiblePairsGreenEye;
var enableParticleEffects = _configService.Current.EnableParticleEffects;
using (var behaviorTree = BeginGeneralTree("Behavior", UIColors.Get("LightlessPurple")))
{
if (behaviorTree.Visible)
{
if (ImGui.Checkbox("Enable Particle Effects", ref enableParticleEffects))
{
_configService.Current.EnableParticleEffects = enableParticleEffects;
_configService.Save();
}
_uiShared.DrawHelpText("This will enable particle effects in the UI.");
if (ImGui.Checkbox("Enable Game Right Click Menu Entries", ref enableRightClickMenu))
{
_configService.Current.EnableRightClickMenus = enableRightClickMenu;
@@ -2859,16 +3011,21 @@ public class SettingsUi : WindowMediatorSubscriberBase
var colorNames = new[]
{
("LightlessPurple", "Primary Purple", "Section titles and dividers"),
("LightlessPurpleActive", "Primary Purple (Active)", "Active tabs and hover highlights"),
("LightlessPurpleDefault", "Primary Purple (Inactive)", "Inactive tabs and default dividers"),
("LightlessBlue", "Secondary Blue", "Secondary title colors, visable pairs"),
("LightlessGreen", "Success Green", "Join buttons and success messages"),
("LightlessYellow", "Warning Yellow", "Warning colors"),
("LightlessOrange", "Performance Orange", "Performance notifications and warnings"),
("PairBlue", "Syncshell Blue", "Syncshell headers, toggle highlights, and moderator actions"),
("DimRed", "Error Red", "Error and offline colors")
};
("LightlessPurple", "Primary Purple", "Section titles and dividers"),
("LightlessPurpleActive", "Primary Purple (Active)", "Active tabs and hover highlights"),
("LightlessPurpleDefault", "Primary Purple (Inactive)", "Inactive tabs and default dividers"),
("LightlessBlue", "Secondary Blue", "Secondary title colors, visable pairs"),
("LightlessGreen", "Success Green", "Join buttons and success messages"),
("LightlessYellow", "Warning Yellow", "Warning colors"),
("LightlessOrange", "Performance Orange", "Performance notifications and warnings"),
("PairBlue", "Syncshell Blue", "Syncshell headers, toggle highlights, and moderator actions"),
("DimRed", "Error Red", "Error and offline colors"),
("HeaderGradientTop", "Header Gradient (Top)", "Top color of the animated header background"),
("HeaderGradientBottom", "Header Gradient (Bottom)", "Bottom color of the animated header background"),
("HeaderStaticStar", "Header Stars", "Tint color for the static background stars in the header"),
("HeaderShootingStar", "Header Shooting Star", "Tint color for the shooting star effect"),
};
if (ImGui.BeginTable("##ColorTable", 3,
ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit))
{
@@ -3074,10 +3231,102 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
ImGui.Separator();
ImGui.Dummy(new Vector2(10));
_uiShared.BigText("Animation");
using (var animationTree = BeginGeneralTree("Animation & Bones", UIColors.Get("LightlessPurple")))
{
if (animationTree.Visible)
{
ImGui.TextUnformatted("Animation Options");
var modes = new[]
{
AnimationValidationMode.Unsafe,
AnimationValidationMode.Safe,
AnimationValidationMode.Safest,
};
var labels = new[]
{
"Unsafe",
"Safe (Race)",
"Safest (Race + Bones)",
};
var tooltips = new[]
{
"No validation. Fastest, but may allow incompatible animations (riskier).",
"Validates skeleton race + modded skeleton check (recommended).",
"Requires matching skeleton race + bone compatibility (strictest).",
};
var currentMode = _configService.Current.AnimationValidationMode;
int selectedIndex = Array.IndexOf(modes, currentMode);
if (selectedIndex < 0) selectedIndex = 1;
ImGui.SetNextItemWidth(140 * ImGuiHelpers.GlobalScale);
bool open = ImGui.BeginCombo("Animation validation", labels[selectedIndex]);
if (ImGui.IsItemHovered())
ImGui.SetTooltip(tooltips[selectedIndex]);
if (open)
{
for (int i = 0; i < modes.Length; i++)
{
bool isSelected = (i == selectedIndex);
if (ImGui.Selectable(labels[i], isSelected))
{
selectedIndex = i;
_configService.Current.AnimationValidationMode = modes[i];
_configService.Save();
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(tooltips[i]);
if (isSelected)
ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
}
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
var cfg = _configService.Current;
bool oneBased = cfg.AnimationAllowOneBasedShift;
if (ImGui.Checkbox("Treat 1-based PAP indices as compatible", ref oneBased))
{
cfg.AnimationAllowOneBasedShift = oneBased;
_configService.Save();
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Fixes off-by-one PAPs (one bone differance in bones and PAP). Can also increase crashing, toggle off if alot of crashing is happening");
bool neighbor = cfg.AnimationAllowNeighborIndexTolerance;
if (ImGui.Checkbox("Allow 1+- bone index tolerance", ref neighbor))
{
cfg.AnimationAllowNeighborIndexTolerance = neighbor;
_configService.Save();
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Looser matching on bone matching. Can reduce false blocks happening but also reduces safety and more prone to crashing.");
ImGui.TreePop();
animationTree.MarkContentEnd();
}
}
ImGui.EndChild();
ImGui.EndGroup();
ImGui.Separator();
generalSelune.DrawHighlightOnly(ImGui.GetIO().DeltaTime);
}
}
@@ -3167,6 +3416,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
return 1f - (elapsed / GeneralTreeHighlightDuration);
}
[StructLayout(LayoutKind.Auto)]
private struct GeneralTreeScope : IDisposable
{
private readonly bool _visible;
@@ -3474,7 +3724,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.DrawHelpText("Controls whether Lightless reduces index textures that exceed the size limit.");
var dimensionOptions = new[] { 512, 1024, 2048, 4096 };
var optionLabels = dimensionOptions.Select(static value => value.ToString()).ToArray();
var optionLabels = dimensionOptions.Select(selector: static value => value.ToString()).ToArray();
var currentDimension = textureConfig.TextureDownscaleMaxDimension;
var selectedIndex = Array.IndexOf(dimensionOptions, currentDimension);
if (selectedIndex < 0)
@@ -3500,6 +3750,14 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.SameLine();
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective VRAM usage information will not work.", UIColors.Get("LightlessYellow")));
var skipPreferredDownscale = textureConfig.SkipTextureDownscaleForPreferredPairs;
if (ImGui.Checkbox("Skip downscale for preferred/direct pairs", ref skipPreferredDownscale))
{
textureConfig.SkipTextureDownscaleForPreferredPairs = skipPreferredDownscale;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("When enabled, textures for direct pairs with preferred permissions are left untouched.");
if (!textureConfig.EnableNonIndexTextureMipTrim && !textureConfig.EnableIndexTextureDownscale)
{
UiSharedService.ColorTextWrapped("Both trimming and downscale are disabled. Lightless will keep original textures regardless of size.", UIColors.Get("DimRed"));
@@ -3527,6 +3785,160 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.TreePop();
}
ImGui.Separator();
if (_uiShared.MediumTreeNode("Model Optimization", UIColors.Get("DimRed")))
{
_uiShared.MediumText("Warning", UIColors.Get("DimRed"));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Model decimation is a "),
new SeStringUtils.RichTextEntry("destructive", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" process and may cause broken or incorrect character appearances."));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("This feature is encouraged to help "),
new SeStringUtils.RichTextEntry("lower-end systems with limited VRAM", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry(" and for use in "),
new SeStringUtils.RichTextEntry("performance-critical scenarios", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry("."));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Runtime decimation "),
new SeStringUtils.RichTextEntry("MAY", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" cause higher load on the system when processing downloads."));
_uiShared.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("When enabled, we cannot provide support for appearance issues caused by this setting!", UIColors.Get("DimRed"), true));
ImGui.Dummy(new Vector2(15));
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessGreen"),
new SeStringUtils.RichTextEntry("If a mesh exceeds the "),
new SeStringUtils.RichTextEntry("triangle threshold", UIColors.Get("LightlessGreen"), true),
new SeStringUtils.RichTextEntry(", it will be decimated automatically to the set "),
new SeStringUtils.RichTextEntry("target triangle ratio", UIColors.Get("LightlessGreen"), true),
new SeStringUtils.RichTextEntry(". This will reduce quality of the mesh or may break it's intended structure."));
var performanceConfig = _playerPerformanceConfigService.Current;
var enableDecimation = performanceConfig.EnableModelDecimation;
if (ImGui.Checkbox("Enable model decimation", ref enableDecimation))
{
performanceConfig.EnableModelDecimation = enableDecimation;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("When enabled, Lightless generates a decimated copy of given model after download.");
var keepOriginalModels = performanceConfig.KeepOriginalModelFiles;
if (ImGui.Checkbox("Keep original model files", ref keepOriginalModels))
{
performanceConfig.KeepOriginalModelFiles = keepOriginalModels;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("When disabled, Lightless removes the original model after a decimated copy is created.");
ImGui.SameLine();
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective triangle usage information will not work.", UIColors.Get("LightlessYellow")));
var skipPreferredDecimation = performanceConfig.SkipModelDecimationForPreferredPairs;
if (ImGui.Checkbox("Skip decimation for preferred/direct pairs", ref skipPreferredDecimation))
{
performanceConfig.SkipModelDecimationForPreferredPairs = skipPreferredDecimation;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("When enabled, models for direct pairs with preferred permissions are left untouched.");
var triangleThreshold = performanceConfig.ModelDecimationTriangleThreshold;
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
if (ImGui.SliderInt("Decimate models above", ref triangleThreshold, 8_000, 100_000))
{
performanceConfig.ModelDecimationTriangleThreshold = Math.Clamp(triangleThreshold, 8_000, 100_000);
_playerPerformanceConfigService.Save();
}
ImGui.SameLine();
ImGui.Text("triangles");
_uiShared.DrawHelpText($"Models below this triangle count are left untouched.{UiSharedService.TooltipSeparator}Default: 50,000");
var targetPercent = (float)(performanceConfig.ModelDecimationTargetRatio * 100.0);
var clampedPercent = Math.Clamp(targetPercent, 60f, 99f);
if (Math.Abs(clampedPercent - targetPercent) > float.Epsilon)
{
performanceConfig.ModelDecimationTargetRatio = clampedPercent / 100.0;
_playerPerformanceConfigService.Save();
targetPercent = clampedPercent;
}
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
if (ImGui.SliderFloat("Target triangle ratio", ref targetPercent, 60f, 99f, "%.0f%%"))
{
performanceConfig.ModelDecimationTargetRatio = Math.Clamp(targetPercent / 100f, 0.6f, 0.99f);
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText($"Target ratio relative to original triangle count (80% keeps 80% of triangles).{UiSharedService.TooltipSeparator}Default: 80%");
ImGui.Dummy(new Vector2(15));
ImGui.TextUnformatted("Decimation targets");
_uiShared.DrawHelpText("Hair mods are always excluded from decimation.");
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessGreen"),
new SeStringUtils.RichTextEntry("Automatic decimation will only target the selected "),
new SeStringUtils.RichTextEntry("decimation targets", UIColors.Get("LightlessGreen"), true),
new SeStringUtils.RichTextEntry("."));
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"),
new SeStringUtils.RichTextEntry("It is advised to not decimate any body related meshes which includes: "),
new SeStringUtils.RichTextEntry("facial mods + sculpts, chest, legs, hands and feet", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry("."));
_uiShared.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Remember, automatic decimation is not perfect and can cause meshes to be ruined, especially hair mods.", UIColors.Get("DimRed"), true));
var allowBody = performanceConfig.ModelDecimationAllowBody;
if (ImGui.Checkbox("Body", ref allowBody))
{
performanceConfig.ModelDecimationAllowBody = allowBody;
_playerPerformanceConfigService.Save();
}
var allowFaceHead = performanceConfig.ModelDecimationAllowFaceHead;
if (ImGui.Checkbox("Face/head", ref allowFaceHead))
{
performanceConfig.ModelDecimationAllowFaceHead = allowFaceHead;
_playerPerformanceConfigService.Save();
}
var allowTail = performanceConfig.ModelDecimationAllowTail;
if (ImGui.Checkbox("Tails/Ears", ref allowTail))
{
performanceConfig.ModelDecimationAllowTail = allowTail;
_playerPerformanceConfigService.Save();
}
var allowClothing = performanceConfig.ModelDecimationAllowClothing;
if (ImGui.Checkbox("Clothing (body/legs/shoes/gloves/hats)", ref allowClothing))
{
performanceConfig.ModelDecimationAllowClothing = allowClothing;
_playerPerformanceConfigService.Save();
}
var allowAccessories = performanceConfig.ModelDecimationAllowAccessories;
if (ImGui.Checkbox("Accessories (earring/rings/bracelet/necklace)", ref allowAccessories))
{
performanceConfig.ModelDecimationAllowAccessories = allowAccessories;
_playerPerformanceConfigService.Save();
}
ImGui.Dummy(new Vector2(5));
UiSharedService.ColoredSeparator(UIColors.Get("LightlessGrey"), 3f);
ImGui.Dummy(new Vector2(5));
DrawTriangleDecimationCounters();
ImGui.Dummy(new Vector2(5));
UiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.5f);
ImGui.TreePop();
}
ImGui.Separator();
ImGui.Dummy(new Vector2(10));