Merge branch 'master' into master

This commit is contained in:
wolfcomp 2023-01-10 22:47:33 +01:00 committed by GitHub
commit 323aae7858
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 2083 additions and 1198 deletions

View file

@ -69,9 +69,7 @@ jobs:
$branchName = "stg"
}
$dllBytes = [System.IO.File]::ReadAllBytes("$(Get-Location)\scratch\Dalamud.dll")
$assembly = [System.Reflection.Assembly]::Load($dllBytes)
$newVersion = $assembly.GetCustomAttributes([System.Reflection.AssemblyMetadataAttribute]) | Where { $_.GetType() -eq [System.Reflection.AssemblyMetadataAttribute] } | Select -First 1 | Select -ExpandProperty "Value"
$newVersion = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\TEMP_gitver.txt")
Remove-Item -Force -Recurse .\scratch
if (Test-Path -Path $branchName) {

View file

@ -29,6 +29,7 @@
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Dalamud.CorePlugin</AssemblyName>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<Platforms>x64</Platforms>
<LangVersion>10.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@ -29,7 +29,7 @@
<ItemGroup>
<PackageReference Include="Lumina" Version="3.10.0" />
<PackageReference Include="Lumina.Excel" Version="6.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -9,6 +9,7 @@
int wmain(int argc, wchar_t** argv)
{
//logging::start_file_logging("dalamud.injector.boot.log", true);
logging::I("Dalamud Injector, (c) 2021 XIVLauncher Contributors");
logging::I("Built at : " __DATE__ "@" __TIME__);

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
@ -62,7 +62,7 @@
<ItemGroup>
<PackageReference Include="Iced" Version="1.17.0" />
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="PeNet" Version="2.6.4" />
<PackageReference Include="Reloaded.Memory" Version="7.0.0" />
<PackageReference Include="Reloaded.Memory.Buffers" Version="2.0.0" />

View file

@ -332,7 +332,7 @@ namespace Dalamud.Injector
startInfo.BootVehEnabled = true;
startInfo.BootVehFull = args.Contains("--veh-full");
startInfo.NoLoadPlugins = args.Contains("--no-plugin");
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-third-plugin");
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin");
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
startInfo.CrashHandlerShow = args.Contains("--crash-handler-console");

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>

View file

@ -32,7 +32,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.CorePlugin", "Dalam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj", "{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.Generators", "lib\FFXIVClientStructs\FFXIVClientStructs.Generators\FFXIVClientStructs.Generators.csproj", "{05AB2F46-268B-4915-806F-DDF813E2D59D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.InteropSourceGenerators", "lib\FFXIVClientStructs\FFXIVClientStructs.InteropSourceGenerators\FFXIVClientStructs.InteropSourceGenerators.csproj", "{05AB2F46-268B-4915-806F-DDF813E2D59D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "DalamudCrashHandler\DalamudCrashHandler.vcxproj", "{317A264C-920B-44A1-8A34-F3A6827B0705}"
EndProject

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>10.0</LangVersion>
@ -63,7 +63,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CheapLoc" Version="1.1.6" />
<PackageReference Include="CheapLoc" Version="1.1.8" />
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
@ -71,7 +71,7 @@
<PackageReference Include="Lumina.Excel" Version="6.2.1" />
<PackageReference Include="MinSharp" Version="1.0.4" />
<PackageReference Include="MonoMod.RuntimeDetour" Version="21.10.10.01" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
@ -80,10 +80,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="5.0.1" />
<PackageReference Include="System.Resources.Extensions" Version="5.0.0" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" />
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
@ -110,28 +110,42 @@
</ItemGroup>
</Target>
<Target Name="GetGitHash" BeforeTargets="WriteGitHash" Condition="'$(BuildHash)' == ''">
<PropertyGroup>
<!-- temp file for the git version (lives in "obj" folder)-->
<VerFile>$(IntermediateOutputPath)gitver</VerFile>
<VerFileClientStructs>$(IntermediateOutputPath)csver</VerFileClientStructs>
</PropertyGroup>
<PropertyGroup>
<!-- Needed temporarily for CI -->
<TempVerFile>$(OutputPath)TEMP_gitver.txt</TempVerFile>
</PropertyGroup>
<Target Name="GetGitHash" BeforeTargets="WriteGitHash" Condition="'$(BuildHash)'=='' And '$(Configuration)'=='Release'">
<!-- write the hash to the temp file.-->
<Exec Command="git -C &quot;$(ProjectDir.Replace('\','\\'))&quot; describe --long --always --dirty &gt; $(VerFile)" />
<Exec Command="git -C &quot;$(ProjectDir.Replace('\','\\'))\..\lib\FFXIVClientStructs&quot; describe --long --always --dirty &gt; $(VerFileClientStructs)" />
<!-- read the version into the GitVersion itemGroup-->
<ReadLinesFromFile File="$(VerFile)">
<Output TaskParameter="Lines" ItemName="GitVersion" />
</ReadLinesFromFile>
<ReadLinesFromFile File="$(VerFileClientStructs)">
<Output TaskParameter="Lines" ItemName="GitVersionClientStructs" />
</ReadLinesFromFile>
<Exec Command="git -C &quot;$(ProjectDir.Replace('\','\\'))&quot; describe --long --always --dirty" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="DalamudGitDescribeOutput" />
</Exec>
<Exec Command="git -C &quot;$(ProjectDir.Replace('\','\\'))\..\lib\FFXIVClientStructs&quot; describe --long --always --dirty" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="ClientStructsGitDescribeOutput" />
</Exec>
<!-- Set the BuildHash property to contain the GitVersion, if it wasn't already set.-->
<PropertyGroup>
<BuildHash>@(GitVersion)</BuildHash>
<BuildHashClientStructs>@(GitVersionClientStructs)</BuildHashClientStructs>
<BuildHash>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitDescribeOutput), @"\t|\n|\r", ""))</BuildHash>
<BuildHashClientStructs>$([System.Text.RegularExpressions.Regex]::Replace($(ClientStructsGitDescribeOutput), @"\t|\n|\r", ""))</BuildHashClientStructs>
</PropertyGroup>
<!-- Looks like this is the only way to write a file without a carriage return in msbuild... -->
<Exec Command="echo|set /P =&quot;$(BuildHash)&quot; &gt; $(TempVerFile)" IgnoreExitCode="true" />
</Target>
<Target Name="GetGitHashStub" BeforeTargets="WriteGitHash" Condition="'$(BuildHash)'=='' And '$(Configuration)'=='Debug'">
<!-- Set the BuildHash property to contain some placeholder, if it wasn't already set.-->
<PropertyGroup>
<LocalBuildText>Local build at $([System.DateTime]::Now.ToString(yyyyMMdd-hhmmss))</LocalBuildText>
<BuildHash>$(LocalBuildText)</BuildHash>
<BuildHashClientStructs>???</BuildHashClientStructs>
</PropertyGroup>
<!-- Looks like this is the only way to write a file without a carriage return in msbuild... -->
<Exec Command="echo|set /P =&quot;$(BuildHash)&quot; &gt; $(TempVerFile)" IgnoreExitCode="true" />
</Target>
<Target Name="WriteGitHash" BeforeTargets="CoreCompile">
<!-- names the obj/.../CustomAssemblyInfo.cs file -->
<PropertyGroup>

View file

@ -63,7 +63,7 @@ public sealed class ClientState : IDisposable, IServiceType
public event EventHandler<ushort> TerritoryChanged;
/// <summary>
/// Event that fires when a character is logging in.
/// Event that fires when a character is logging in, and the local character object is available.
/// </summary>
public event EventHandler Login;
@ -167,7 +167,7 @@ public sealed class ClientState : IDisposable, IServiceType
if (condition == null || gameGui == null || data == null)
return;
if (condition.Any() && this.lastConditionNone == true)
if (condition.Any() && this.lastConditionNone == true && this.LocalPlayer != null)
{
Log.Debug("Is login");
this.lastConditionNone = false;

View file

@ -114,7 +114,7 @@ public sealed class ClientStateAddressResolver : BaseAddressResolver
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 41 8D 50 77 E8 ?? ?? ?? ?? 48 8B 5C 24");
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");

View file

@ -46,7 +46,7 @@ public sealed unsafe partial class PartyList : IServiceType
/// <summary>
/// Gets a value indicating whether this group is an alliance.
/// </summary>
public bool IsAlliance => this.GroupManagerStruct->IsAlliance;
public bool IsAlliance => this.GroupManagerStruct->AllianceFlags > 0;
/// <summary>
/// Gets the address of the Group Manager.

View file

@ -49,6 +49,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
/// <param name="text2">Text2 passed to the native flytext function.</param>
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
/// <param name="damageTypeIcon">Damage Type Icon ID passed to the native flytext function. Displayed next to damage values to denote damage type.</param>
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
@ -60,6 +61,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
ref SeString text2,
ref uint color,
ref uint icon,
ref uint damageTypeIcon,
ref float yOffset,
ref bool handled);
@ -74,6 +76,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
IntPtr text2,
uint color,
uint icon,
uint damageTypeIcon,
IntPtr text1,
float yOffset);
@ -120,7 +123,8 @@ public sealed class FlyTextGui : IDisposable, IServiceType
/// <param name="text2">Text2 passed to the native flytext function.</param>
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon)
/// <param name="damageTypeIcon">Damage Type Icon ID passed to the native flytext function. Displayed next to damage values to denote damage type.</param>
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon, uint damageTypeIcon)
{
// Known valid flytext region within the atk arrays
var numIndex = 28;
@ -134,7 +138,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
return;
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
var flytext = gameGui.GetAddonByName("_FlyText", 1);
var flytext = gameGui.GetAddonByName("_FlyText");
if (ui == null || flytext == IntPtr.Zero)
return;
@ -149,11 +153,12 @@ public sealed class FlyTextGui : IDisposable, IServiceType
numArray->IntArray[numOffset + 1] = (int)kind;
numArray->IntArray[numOffset + 2] = unchecked((int)val1);
numArray->IntArray[numOffset + 3] = unchecked((int)val2);
numArray->IntArray[numOffset + 4] = 5; // Unknown
numArray->IntArray[numOffset + 5] = unchecked((int)color);
numArray->IntArray[numOffset + 6] = unchecked((int)icon);
numArray->IntArray[numOffset + 7] = 0; // Unknown
numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset
numArray->IntArray[numOffset + 4] = unchecked((int)damageTypeIcon); // Icons for damage type
numArray->IntArray[numOffset + 5] = 5; // Unknown
numArray->IntArray[numOffset + 6] = unchecked((int)color);
numArray->IntArray[numOffset + 7] = unchecked((int)icon);
numArray->IntArray[numOffset + 8] = 0; // Unknown
numArray->IntArray[numOffset + 9] = 0; // Unknown, has something to do with yOffset
fixed (byte* pText1 = text1.Encode())
{
@ -200,6 +205,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
IntPtr text2,
uint color,
uint icon,
uint damageTypeIcon,
IntPtr text1,
float yOffset)
{
@ -217,13 +223,14 @@ public sealed class FlyTextGui : IDisposable, IServiceType
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
var tmpColor = color;
var tmpIcon = icon;
var tmpDamageTypeIcon = damageTypeIcon;
var tmpYOffset = yOffset;
var cmpText1 = tmpText1.ToString();
var cmpText2 = tmpText2.ToString();
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
$"kind({kind}) val1({val1}) val2({val2}) " +
$"kind({kind}) val1({val1}) val2({val2}) damageTypeIcon({damageTypeIcon}) " +
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
$"color({color:X}) icon({icon}) yOffset({yOffset})");
Log.Verbose("[FlyText] Calling flytext events!");
@ -235,6 +242,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
ref tmpText2,
ref tmpColor,
ref tmpIcon,
ref tmpDamageTypeIcon,
ref tmpYOffset,
ref handled);
@ -262,7 +270,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
if (!dirty)
{
Log.Verbose("[FlyText] Calling flytext with original args.");
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset);
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, damageTypeIcon, text1, yOffset);
}
var terminated1 = Terminate(tmpText1.Encode());
@ -281,6 +289,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
pText2,
tmpColor,
tmpIcon,
tmpDamageTypeIcon,
pText1,
tmpYOffset);

View file

@ -26,6 +26,6 @@ public class FlyTextGuiAddressResolver : BaseAddressResolver
protected override void Setup64Bit(SigScanner sig)
{
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
this.CreateFlyText = sig.ScanText("40 53 55 41 56 48 83 EC 40 48 63 EA");
}
}

View file

@ -323,7 +323,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
/// <param name="name">Name of addon to find.</param>
/// <param name="index">Index of addon to find (1-indexed).</param>
/// <returns>IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the addon.</returns>
public unsafe IntPtr GetAddonByName(string name, int index)
public unsafe IntPtr GetAddonByName(string name, int index = 1)
{
var atkStage = FFXIVClientStructs.FFXIV.Component.GUI.AtkStage.GetSingleton();
if (atkStage == null)
@ -363,7 +363,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
/// </summary>
/// <param name="addonPtr">The addon address.</param>
/// <returns>A pointer to the agent interface.</returns>
public unsafe IntPtr FindAgentInterface(IntPtr addonPtr)
public IntPtr FindAgentInterface(IntPtr addonPtr)
{
if (addonPtr == IntPtr.Zero)
return IntPtr.Zero;
@ -411,11 +411,26 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
this.utf8StringFromSequenceHook.Dispose();
}
/// <summary>
/// Indicates if the game is on the title screen.
/// </summary>
/// <returns>A value indicating whether or not the game is on the title screen.</returns>
internal bool IsOnTitleScreen()
{
var charaSelect = this.GetAddonByName("CharaSelect", 1);
var charaMake = this.GetAddonByName("CharaMake", 1);
var titleDcWorldMap = this.GetAddonByName("TitleDCWorldMap", 1);
if (charaMake != nint.Zero || charaSelect != nint.Zero || titleDcWorldMap != nint.Zero)
return false;
return !Service<ClientState.ClientState>.Get().IsLoggedIn;
}
/// <summary>
/// Set the current background music.
/// </summary>
/// <param name="bgmKey">The background music key.</param>
public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
internal void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
/// <summary>
/// Reset the stored "UI hide" state.

View file

@ -101,11 +101,6 @@ internal class DalamudCommands : IServiceType
HelpMessage = Loc.Localize("DalamudInstallerHelp", "Open the plugin installer"),
});
commandManager.AddHandler("/xlcredits", new CommandInfo(this.OnOpenCreditsCommand)
{
HelpMessage = Loc.Localize("DalamudCreditsHelp", "Opens the credits for dalamud."),
});
commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand)
{
HelpMessage =
@ -328,11 +323,6 @@ internal class DalamudCommands : IServiceType
Service<DalamudInterface>.Get().TogglePluginInstallerWindow();
}
private void OnOpenCreditsCommand(string command, string arguments)
{
Service<DalamudInterface>.Get().ToggleCreditsWindow();
}
private void OnSetLanguageCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();

View file

@ -12,11 +12,13 @@ using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui;
using Dalamud.Game.Internal;
using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Windows;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
using Dalamud.Interface.Internal.Windows.SelfTest;
using Dalamud.Interface.Internal.Windows.Settings;
using Dalamud.Interface.Internal.Windows.StyleEditor;
using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing;
@ -40,12 +42,13 @@ namespace Dalamud.Interface.Internal;
[ServiceManager.EarlyLoadedService]
internal class DalamudInterface : IDisposable, IServiceType
{
private const float CreditsDarkeningMaxAlpha = 0.8f;
private static readonly ModuleLog Log = new("DUI");
private readonly ChangelogWindow changelogWindow;
private readonly ColorDemoWindow colorDemoWindow;
private readonly ComponentDemoWindow componentDemoWindow;
private readonly CreditsWindow creditsWindow;
private readonly DataWindow dataWindow;
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
private readonly ImeWindow imeWindow;
@ -62,6 +65,9 @@ internal class DalamudInterface : IDisposable, IServiceType
private readonly TextureWrap logoTexture;
private readonly TextureWrap tsmLogoTexture;
private bool isCreditsDarkening = false;
private OutCubic creditsDarkeningAnimation = new(TimeSpan.FromSeconds(10));
#if DEBUG
private bool isImGuiDrawDevMenu = true;
#else
@ -90,7 +96,6 @@ internal class DalamudInterface : IDisposable, IServiceType
this.changelogWindow = new ChangelogWindow() { IsOpen = false };
this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
this.creditsWindow = new CreditsWindow() { IsOpen = false };
this.dataWindow = new DataWindow() { IsOpen = false };
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
this.imeWindow = new ImeWindow() { IsOpen = false };
@ -107,7 +112,6 @@ internal class DalamudInterface : IDisposable, IServiceType
this.WindowSystem.AddWindow(this.changelogWindow);
this.WindowSystem.AddWindow(this.colorDemoWindow);
this.WindowSystem.AddWindow(this.componentDemoWindow);
this.WindowSystem.AddWindow(this.creditsWindow);
this.WindowSystem.AddWindow(this.dataWindow);
this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow);
this.WindowSystem.AddWindow(this.imeWindow);
@ -147,6 +151,9 @@ internal class DalamudInterface : IDisposable, IServiceType
{
tsm.AddEntryCore(Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), this.tsmLogoTexture, () => this.isImGuiDrawDevMenu = true);
}
this.creditsDarkeningAnimation.Point1 = Vector2.Zero;
this.creditsDarkeningAnimation.Point2 = new Vector2(CreditsDarkeningMaxAlpha);
}
/// <summary>
@ -176,7 +183,6 @@ internal class DalamudInterface : IDisposable, IServiceType
this.WindowSystem.RemoveAllWindows();
this.changelogWindow.Dispose();
this.creditsWindow.Dispose();
this.consoleWindow.Dispose();
this.pluginWindow.Dispose();
this.titleScreenMenuWindow.Dispose();
@ -202,11 +208,6 @@ internal class DalamudInterface : IDisposable, IServiceType
/// </summary>
public void OpenComponentDemoWindow() => this.componentDemoWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="CreditsWindow"/>.
/// </summary>
public void OpenCreditsWindow() => this.creditsWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="DataWindow"/>.
/// </summary>
@ -313,11 +314,6 @@ internal class DalamudInterface : IDisposable, IServiceType
/// </summary>
public void ToggleComponentDemoWindow() => this.componentDemoWindow.Toggle();
/// <summary>
/// Toggles the <see cref="CreditsWindow"/>.
/// </summary>
public void ToggleCreditsWindow() => this.creditsWindow.Toggle();
/// <summary>
/// Toggles the <see cref="DataWindow"/>.
/// </summary>
@ -388,6 +384,18 @@ internal class DalamudInterface : IDisposable, IServiceType
#endregion
/// <summary>
/// Toggle the screen darkening effect used for the credits.
/// </summary>
/// <param name="status">Whether or not to turn the effect on.</param>
public void SetCreditsDarkeningAnimation(bool status)
{
this.isCreditsDarkening = status;
if (status)
this.creditsDarkeningAnimation.Restart();
}
private void OnDraw()
{
this.FrameCount++;
@ -430,6 +438,9 @@ internal class DalamudInterface : IDisposable, IServiceType
if (this.isImGuiTestWindowsInMonospace)
ImGui.PopFont();
if (this.isCreditsDarkening)
this.DrawCreditsDarkeningAnimation();
// Release focus of any ImGui window if we click into the game.
var io = ImGui.GetIO();
if (!io.WantCaptureMouse && (User32.GetKeyState((int)User32.VirtualKey.VK_LBUTTON) & 0x8000) != 0)
@ -443,6 +454,26 @@ internal class DalamudInterface : IDisposable, IServiceType
}
}
private void DrawCreditsDarkeningAnimation()
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0f);
ImGui.SetNextWindowPos(new Vector2(0, 0));
ImGui.SetNextWindowSize(ImGuiHelpers.MainViewport.Size);
ImGuiHelpers.ForceNextWindowMainViewport();
this.creditsDarkeningAnimation.Update();
ImGui.SetNextWindowBgAlpha(Math.Min(this.creditsDarkeningAnimation.EasedPoint.X, CreditsDarkeningMaxAlpha));
ImGui.Begin(
"###CreditsDarkenWindow",
ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBringToFrontOnFocus |
ImGuiWindowFlags.NoNav);
ImGui.End();
ImGui.PopStyleVar();
}
private void DrawHiddenDevMenuOpener()
{
var condition = Service<Condition>.Get();
@ -573,11 +604,6 @@ internal class DalamudInterface : IDisposable, IServiceType
this.OpenDataWindow();
}
if (ImGui.MenuItem("Open Credits window"))
{
this.OpenCreditsWindow();
}
if (ImGui.MenuItem("Open Settings window"))
{
this.OpenSettings();
@ -671,6 +697,7 @@ internal class DalamudInterface : IDisposable, IServiceType
ImGui.MenuItem(Util.AssemblyVersion, false);
ImGui.MenuItem(startInfo.GameVersion.ToString(), false);
ImGui.MenuItem($"D: {Util.GetGitHash()} CS: {Util.GetGitHashClientStructs()}", false);
ImGui.MenuItem($"CLR: {Environment.Version}", false);
ImGui.EndMenu();
}
@ -848,7 +875,12 @@ internal class DalamudInterface : IDisposable, IServiceType
ImGui.BeginMenu(Util.GetGitHash(), false);
ImGui.BeginMenu(this.FrameCount.ToString("000000"), false);
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
ImGui.BeginMenu($"{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
var videoMem = Service<InterfaceManager>.Get().GetD3dMemoryInfo();
ImGui.BeginMenu(
!videoMem.HasValue ? $"V:???" : $"V:{Util.FormatBytes(videoMem.Value.Used)}",
false);
ImGui.PopFont();
}

View file

@ -0,0 +1,54 @@
using System;
using ImGuiScene;
namespace Dalamud.Interface.Internal;
/// <summary>
/// Safety harness for ImGuiScene textures that will defer destruction until
/// the end of the frame.
/// </summary>
public class DalamudTextureWrap : TextureWrap
{
private readonly TextureWrap wrappedWrap;
/// <summary>
/// Initializes a new instance of the <see cref="DalamudTextureWrap"/> class.
/// </summary>
/// <param name="wrappingWrap">The texture wrap to wrap.</param>
internal DalamudTextureWrap(TextureWrap wrappingWrap)
{
this.wrappedWrap = wrappingWrap;
}
/// <summary>
/// Gets the ImGui handle of the texture.
/// </summary>
public IntPtr ImGuiHandle => this.wrappedWrap.ImGuiHandle;
/// <summary>
/// Gets the width of the texture.
/// </summary>
public int Width => this.wrappedWrap.Width;
/// <summary>
/// Gets the height of the texture.
/// </summary>
public int Height => this.wrappedWrap.Height;
/// <summary>
/// Queue the texture to be disposed once the frame ends.
/// </summary>
public void Dispose()
{
Service<InterfaceManager>.Get().EnqueueDeferredDispose(this);
}
/// <summary>
/// Actually dispose the wrapped texture.
/// </summary>
internal void RealDispose()
{
this.wrappedWrap.Dispose();
}
}

View file

@ -26,7 +26,6 @@ using ImGuiNET;
using ImGuiScene;
using PInvoke;
using Serilog;
using SharpDX.Direct3D11;
// general dev notes, here because it's easiest
@ -56,6 +55,8 @@ internal class InterfaceManager : IDisposable, IServiceType
private readonly HashSet<SpecialGlyphRequest> glyphRequests = new();
private readonly Dictionary<ImFontPtr, TargetFontModification> loadedFontInfo = new();
private readonly List<DalamudTextureWrap> deferredDisposeTextures = new();
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get();
@ -145,7 +146,7 @@ internal class InterfaceManager : IDisposable, IServiceType
/// <summary>
/// Gets the D3D11 device instance.
/// </summary>
public Device? Device => this.scene?.Device;
public SharpDX.Direct3D11.Device? Device => this.scene?.Device;
/// <summary>
/// Gets the address handle to the main process window.
@ -242,7 +243,8 @@ internal class InterfaceManager : IDisposable, IServiceType
try
{
return this.scene?.LoadImage(filePath) ?? null;
var wrap = this.scene?.LoadImage(filePath);
return wrap != null ? new DalamudTextureWrap(wrap) : null;
}
catch (Exception ex)
{
@ -264,7 +266,8 @@ internal class InterfaceManager : IDisposable, IServiceType
try
{
return this.scene?.LoadImage(imageData) ?? null;
var wrap = this.scene?.LoadImage(imageData);
return wrap != null ? new DalamudTextureWrap(wrap) : null;
}
catch (Exception ex)
{
@ -289,7 +292,8 @@ internal class InterfaceManager : IDisposable, IServiceType
try
{
return this.scene?.LoadImageRaw(imageData, width, height, numChannels) ?? null;
var wrap = this.scene?.LoadImageRaw(imageData, width, height, numChannels);
return wrap != null ? new DalamudTextureWrap(wrap) : null;
}
catch (Exception ex)
{
@ -395,6 +399,33 @@ internal class InterfaceManager : IDisposable, IServiceType
return this.NewFontSizeRef(size, ranges);
}
/// <summary>
/// Enqueue a texture to be disposed at the end of the frame.
/// </summary>
/// <param name="wrap">The texture.</param>
public void EnqueueDeferredDispose(DalamudTextureWrap wrap)
{
this.deferredDisposeTextures.Add(wrap);
}
/// <summary>
/// Get video memory information.
/// </summary>
/// <returns>The currently used video memory, or null if not available.</returns>
public (long Used, long Available)? GetD3dMemoryInfo()
{
if (this.Device == null)
return null;
var dxgiDev = this.Device.QueryInterface<SharpDX.DXGI.Device>();
var dxgiAdapter = dxgiDev.Adapter.QueryInterfaceOrNull<SharpDX.DXGI.Adapter4>();
if (dxgiAdapter == null)
return null;
var memInfo = dxgiAdapter.QueryVideoMemoryInfo(0, SharpDX.DXGI.MemorySegmentGroup.Local);
return (memInfo.CurrentUsage, memInfo.CurrentReservation);
}
private static void ShowFontError(string path)
{
Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error");
@ -549,6 +580,13 @@ internal class InterfaceManager : IDisposable, IServiceType
this.RenderImGui();
foreach (var texture in this.deferredDisposeTextures)
{
texture.RealDispose();
}
this.deferredDisposeTextures.Clear();
return this.presentHook.Original(swapChain, syncInterval, presentFlags);
}

View file

@ -52,7 +52,7 @@ internal class ConsoleWindow : Window, IDisposable
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
/// </summary>
public ConsoleWindow()
: base("Dalamud Console")
: base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
{
var configuration = Service<DalamudConfiguration>.Get();

View file

@ -99,6 +99,7 @@ internal class DataWindow : Window
private string flyText1 = string.Empty;
private string flyText2 = string.Empty;
private int flyIcon;
private int flyDmgIcon;
private Vector4 flyColor = new(1, 0, 0, 1);
// ImGui fields
@ -450,6 +451,10 @@ internal class DataWindow : Window
{
ImGui.TextUnformatted("LocalPlayer null.");
}
else if (clientState.IsPvPExcludingDen)
{
ImGui.TextUnformatted("Cannot access object table while in PvP.");
}
else
{
stateString += $"ObjectTableLen: {objectTable.Length}\n";
@ -1157,6 +1162,7 @@ internal class DataWindow : Window
ImGui.InputInt("Val2", ref this.flyVal2);
ImGui.InputInt("Icon ID", ref this.flyIcon);
ImGui.InputInt("Damage Icon ID", ref this.flyDmgIcon);
ImGui.ColorEdit4("Color", ref this.flyColor);
ImGui.InputInt("Actor Index", ref this.flyActor);
var sendColor = ImGui.ColorConvertFloat4ToU32(this.flyColor);
@ -1171,7 +1177,8 @@ internal class DataWindow : Window
this.flyText1,
this.flyText2,
sendColor,
unchecked((uint)this.flyIcon));
unchecked((uint)this.flyIcon),
unchecked((uint)this.flyDmgIcon));
}
}

View file

@ -1465,7 +1465,7 @@ internal class PluginInstallerWindow : Window, IDisposable
return ready;
}
private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, bool installableOutdated, Action drawContextMenuAction, int index)
private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, bool installableOutdated, bool isOrphan, Action drawContextMenuAction, int index)
{
ImGui.Separator();
@ -1536,7 +1536,7 @@ internal class PluginInstallerWindow : Window, IDisposable
if (updateAvailable)
ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize);
else if (trouble && !pluginDisabled)
else if ((trouble && !pluginDisabled) || isOrphan)
ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize);
else if (installableOutdated)
ImGui.Image(this.imageCache.OutdatedInstallableIcon.ImGuiHandle, iconSize);
@ -1600,6 +1600,18 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.TextWrapped(Locs.PluginBody_Orphaned);
ImGui.PopStyleColor();
}
else if (plugin is { IsDecommissioned: true } && !plugin.Manifest.IsThirdParty)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.TextWrapped(Locs.PluginBody_NoServiceOfficial);
ImGui.PopStyleColor();
}
else if (plugin is { IsDecommissioned: true } && plugin.Manifest.IsThirdParty)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.TextWrapped(Locs.PluginBody_NoServiceThird);
ImGui.PopStyleColor();
}
else if (plugin != null && !plugin.CheckPolicy())
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
@ -1617,7 +1629,7 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.SetCursorPosX(cursor.X);
// Description
if (plugin is null or { IsOutdated: false, IsBanned: false })
if (plugin is null or { IsOutdated: false, IsBanned: false } && !trouble)
{
if (!string.IsNullOrWhiteSpace(manifest.Punchline))
{
@ -1737,7 +1749,7 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.PushID($"available{index}{manifest.InternalName}");
var isThirdParty = manifest.SourceRepo.IsThirdParty;
if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, isOutdated, () => this.DrawAvailablePluginContextMenu(manifest), index))
if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, isOutdated, false, () => this.DrawAvailablePluginContextMenu(manifest), index))
{
if (!wasSeen)
configuration.SeenPluginInternalName.Add(manifest.InternalName);
@ -1978,6 +1990,13 @@ internal class PluginInstallerWindow : Window, IDisposable
trouble = true;
}
// Out of service
if (plugin.IsDecommissioned && !plugin.IsOrphaned)
{
label += Locs.PluginTitleMod_NoService;
trouble = true;
}
// Scheduled for deletion
if (plugin.Manifest.ScheduledForDeletion)
{
@ -1987,7 +2006,7 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}");
var hasChangelog = !plugin.Manifest.Changelog.IsNullOrEmpty();
if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, false, () => this.DrawInstalledPluginContextMenu(plugin, testingOptIn), index))
if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, false, plugin.IsOrphaned, () => this.DrawInstalledPluginContextMenu(plugin, testingOptIn), index))
{
if (!this.WasPluginSeen(plugin.Manifest.InternalName))
configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName);
@ -2188,7 +2207,8 @@ internal class PluginInstallerWindow : Window, IDisposable
disabled = disabled || (plugin.IsOutdated && !pluginManager.LoadAllApiLevels) || plugin.IsBanned;
// Disable everything if the plugin is orphaned
disabled = disabled || plugin.IsOrphaned;
// Control will immediately be disabled once the plugin is disabled
disabled = disabled || (plugin.IsOrphaned && !plugin.IsLoaded);
// Disable everything if the plugin failed to load
disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
@ -2202,7 +2222,7 @@ internal class PluginInstallerWindow : Window, IDisposable
StyleModelV1.DalamudStandard.Push();
if (plugin.State == PluginState.UnloadError)
if (plugin.State == PluginState.UnloadError && !plugin.IsDev)
{
ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown);
@ -2874,6 +2894,8 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string PluginTitleMod_Disabled => Loc.Localize("InstallerDisabled", " (disabled)");
public static string PluginTitleMod_NoService => Loc.Localize("InstallerNoService", " (decommissioned)");
public static string PluginTitleMod_Unloaded => Loc.Localize("InstallerUnloaded", " (unloaded)");
public static string PluginTitleMod_HasUpdate => Loc.Localize("InstallerHasUpdate", " (has update)");
@ -2942,6 +2964,10 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string PluginBody_Orphaned => Loc.Localize("InstallerOrphanedPluginBody ", "This plugin's source repository is no longer available. You may need to reinstall it from its repository, or re-add the repository.");
public static string PluginBody_NoServiceOfficial => Loc.Localize("InstallerNoServiceOfficialPluginBody", "This plugin is no longer being maintained. It will still work, but there will be no further updates and you can't reinstall it.");
public static string PluginBody_NoServiceThird => Loc.Localize("InstallerNoServiceThirdPluginBody", "This plugin is no longer being serviced by its source repo. You may have to look for an updated version in another repo.");
public static string PluginBody_LoadFailed => Loc.Localize("InstallerLoadFailedPluginBody ", "This plugin failed to load. Please contact the author for more information.");
public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin was automatically disabled due to incompatibilities and is not available at the moment. Please wait for it to be updated by its author.");

View file

@ -10,7 +10,9 @@ using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility;
using ImGuiNET;
using Serilog;
namespace Dalamud.Interface.Internal.Windows;
@ -20,6 +22,7 @@ namespace Dalamud.Interface.Internal.Windows;
internal class PluginStatWindow : Window
{
private bool showDalamudHooks;
private string hookSearchText = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="PluginStatWindow"/> class.
@ -211,6 +214,12 @@ internal class PluginStatWindow : Window
{
ImGui.Checkbox("Show Dalamud Hooks", ref this.showDalamudHooks);
ImGui.InputTextWithHint(
"###PluginStatWindow_HookSearch",
"Search",
ref this.hookSearchText,
500);
if (ImGui.BeginTable(
"##PluginStatsHooks",
4,
@ -238,6 +247,13 @@ internal class PluginStatWindow : Window
if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly())
continue;
if (!this.hookSearchText.IsNullOrEmpty())
{
if ((trackedHook.Delegate.Target == null || !trackedHook.Delegate.Target.ToString().Contains(this.hookSearchText, StringComparison.OrdinalIgnoreCase))
&& !trackedHook.Delegate.Method.Name.Contains(this.hookSearchText, StringComparison.OrdinalIgnoreCase))
continue;
}
ImGui.TableNextRow();
ImGui.TableNextColumn();
@ -281,7 +297,7 @@ internal class PluginStatWindow : Window
}
catch (Exception ex)
{
ImGui.Text(ex.Message);
Log.Error(ex, "Error drawing hooks in plugin stats");
}
}

View file

@ -0,0 +1,52 @@
using System;
namespace Dalamud.Interface.Internal.Windows.Settings;
/// <summary>
/// Basic, drawable settings entry.
/// </summary>
public abstract class SettingsEntry
{
/// <summary>
/// Gets or sets the public, searchable name of this settings entry.
/// </summary>
public string? Name { get; protected set; }
/// <summary>
/// Gets or sets a value indicating whether or not this entry is valid.
/// </summary>
public virtual bool IsValid { get; protected set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not this entry is visible.
/// </summary>
public virtual bool IsVisible { get; protected set; } = true;
/// <summary>
/// Gets the ID of this settings entry, used for ImGui uniqueness.
/// </summary>
protected Guid Id { get; } = Guid.NewGuid();
/// <summary>
/// Load this setting.
/// </summary>
public abstract void Load();
/// <summary>
/// Save this setting.
/// </summary>
public abstract void Save();
/// <summary>
/// Draw this setting control.
/// </summary>
public abstract void Draw();
/// <summary>
/// Function to be called when the tab is closed.
/// </summary>
public virtual void OnClose()
{
// ignored
}
}

View file

@ -0,0 +1,72 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Interface.Internal.Windows.Settings;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public abstract class SettingsTab : IDisposable
{
public abstract SettingsEntry[] Entries { get; }
public abstract string Title { get; }
public bool IsOpen { get; set; } = false;
public virtual bool IsVisible { get; } = true;
public virtual void OnOpen()
{
// ignored
}
public virtual void OnClose()
{
foreach (var settingsEntry in this.Entries)
{
settingsEntry.OnClose();
}
}
public virtual void Draw()
{
foreach (var settingsEntry in this.Entries)
{
if (settingsEntry.IsVisible)
settingsEntry.Draw();
ImGuiHelpers.ScaledDummy(5);
}
ImGuiHelpers.ScaledDummy(15);
}
public virtual void Load()
{
foreach (var settingsEntry in this.Entries)
{
settingsEntry.Load();
}
}
public virtual void Save()
{
foreach (var settingsEntry in this.Entries)
{
settingsEntry.Save();
}
}
public virtual void Discard()
{
foreach (var settingsEntry in this.Entries)
{
settingsEntry.Load();
}
}
/// <inheritdoc/>
public virtual void Dispose()
{
// ignored
}
}

View file

@ -0,0 +1,237 @@
using System.Linq;
using System.Numerics;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.Windows.Settings.Tabs;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings;
/// <summary>
/// The window that allows for general configuration of Dalamud itself.
/// </summary>
internal class SettingsWindow : Window
{
private readonly SettingsTab[] tabs =
{
new SettingsTabGeneral(),
new SettingsTabLook(),
new SettingsTabDtr(),
new SettingsTabExperimental(),
new SettingsTabAbout(),
};
private string searchInput = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="SettingsWindow"/> class.
/// </summary>
public SettingsWindow()
: base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
{
this.Size = new Vector2(740, 550);
this.SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(752, 610),
MaximumSize = new Vector2(1780, 940),
};
this.SizeCondition = ImGuiCond.FirstUseEver;
}
/// <inheritdoc/>
public override void OnOpen()
{
foreach (var settingsTab in this.tabs)
{
settingsTab.Load();
}
this.searchInput = string.Empty;
base.OnOpen();
}
/// <inheritdoc/>
public override void OnClose()
{
var configuration = Service<DalamudConfiguration>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
var rebuildFont =
ImGui.GetIO().FontGlobalScale != configuration.GlobalUiScale ||
interfaceManager.FontGamma != configuration.FontGammaLevel ||
interfaceManager.UseAxis != configuration.UseAxisFontsFromGame;
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
interfaceManager.FontGammaOverride = null;
interfaceManager.UseAxisOverride = null;
if (rebuildFont)
interfaceManager.RebuildFonts();
foreach (var settingsTab in this.tabs)
{
if (settingsTab.IsOpen)
settingsTab.OnClose();
settingsTab.IsOpen = false;
}
}
/// <inheritdoc/>
public override void Draw()
{
var windowSize = ImGui.GetWindowSize();
if (ImGui.BeginTabBar("###settingsTabs"))
{
if (string.IsNullOrEmpty(this.searchInput))
{
foreach (var settingsTab in this.tabs.Where(x => x.IsVisible))
{
if (ImGui.BeginTabItem(settingsTab.Title))
{
if (!settingsTab.IsOpen)
{
settingsTab.IsOpen = true;
settingsTab.OnOpen();
}
if (ImGui.BeginChild($"###settings_scrolling_{settingsTab.Title}", new Vector2(-1, -1), false))
{
settingsTab.Draw();
}
ImGui.EndChild();
ImGui.EndTabItem();
}
else if (settingsTab.IsOpen)
{
settingsTab.IsOpen = false;
settingsTab.OnClose();
}
}
}
else
{
if (ImGui.BeginTabItem("Search Results"))
{
var any = false;
foreach (var settingsTab in this.tabs.Where(x => x.IsVisible))
{
var eligible = settingsTab.Entries.Where(x => !x.Name.IsNullOrEmpty() && x.Name.ToLower().Contains(this.searchInput.ToLower())).ToArray();
if (!eligible.Any())
continue;
any = true;
ImGui.TextColored(ImGuiColors.DalamudGrey, settingsTab.Title);
ImGui.Dummy(new Vector2(5));
foreach (var settingsTabEntry in eligible)
{
settingsTabEntry.Draw();
ImGuiHelpers.ScaledDummy(3);
}
ImGui.Separator();
ImGui.Dummy(new Vector2(10));
}
if (!any)
ImGui.TextColored(ImGuiColors.DalamudGrey, "No results found...");
ImGui.EndTabItem();
}
}
}
ImGui.SetCursorPos(windowSize - ImGuiHelpers.ScaledVector2(70));
if (ImGui.BeginChild("###settingsFinishButton"))
{
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 100f);
ImGui.PushFont(InterfaceManager.IconFont);
var invalid = this.tabs.Any(x => x.Entries.Any(y => !y.IsValid));
if (invalid)
ImGui.BeginDisabled();
if (ImGui.Button(FontAwesomeIcon.Save.ToIconString(), new Vector2(40)))
{
this.Save();
if (!ImGui.IsKeyDown(ImGuiKey.ModShift))
this.IsOpen = false;
}
ImGui.PopStyleVar();
ImGui.PopFont();
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(!ImGui.IsKeyDown(ImGuiKey.ModShift)
? Loc.Localize("DalamudSettingsSaveAndExit", "Save changes and close")
: Loc.Localize("DalamudSettingsSaveAndExit", "Save changes"));
}
if (invalid)
ImGui.EndDisabled();
}
ImGui.EndChild();
ImGui.SetCursorPos(new Vector2(windowSize.X - 250, ImGui.GetTextLineHeightWithSpacing() + (ImGui.GetStyle().FramePadding.Y * 2)));
ImGui.SetNextItemWidth(240);
ImGui.InputTextWithHint("###searchInput", "Search for settings...", ref this.searchInput, 100);
}
private void Save()
{
var configuration = Service<DalamudConfiguration>.Get();
foreach (var settingsTab in this.tabs)
{
settingsTab.Save();
}
// Apply docking flag
if (!configuration.IsDocking)
{
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable;
}
else
{
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;
}
// NOTE (Chiv) Toggle gamepad navigation via setting
if (!configuration.IsGamepadNavigationEnabled)
{
ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad;
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos;
var di = Service<DalamudInterface>.Get();
di.CloseGamepadModeNotifierWindow();
}
else
{
ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad;
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos;
}
configuration.QueueSave();
_ = Service<PluginManager>.Get().ReloadPluginMastersAsync();
Service<InterfaceManager>.Get().RebuildFonts();
}
}

View file

@ -1,23 +1,22 @@
using System;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Numerics;
using CheapLoc;
using Dalamud.Game.Gui;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
using ImGuiNET;
using ImGuiScene;
namespace Dalamud.Interface.Internal.Windows;
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
/// <summary>
/// A window documenting contributors to the project.
/// </summary>
internal class CreditsWindow : Window, IDisposable
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class SettingsTabAbout : SettingsTab
{
private const float CreditFps = 60.0f;
private const string ThankYouText = "Thank you!";
@ -27,7 +26,8 @@ A FFXIV Plugin Framework
Version D{0}
created by:
Created by:
goat
daemitus
@ -124,8 +124,15 @@ philpax
We use these awesome libraries:
FFXIVClientStructs ({2})
Copyright (c) 2021 aers
Licensed under the MIT License
Lumina by Adam
FFXIVClientStructs by aers ({2})
Licensed under the WTFPL v2.0
Reloaded Libraries by Sewer56
Licensed under the GNU Lesser General Public License v3.0
DotNetCorePlugins
Copyright (c) Nate McMaster
@ -145,17 +152,20 @@ Licensed under the BSD 2-Clause License
SRELL
Copyright (c) 2012-2022, Nozomu Katoo
STB Libraries
Copyright (c) 2017 Sean Barrett
Licensed under the MIT License
Please see licenses.txt for more information.
Thanks to everyone in the XIVLauncher Discord server
Thanks to everyone in the XIVLauncher Discord server!
Join us at: https://discord.gg/3NMcUV5
Dalamud is licensed under AGPL v3 or later
Contribute at: https://github.com/goatsoft/Dalamud
Dalamud is licensed under AGPL v3 or later.
Contribute at: https://github.com/goatcorp/Dalamud
";
private readonly TextureWrap logoTexture;
@ -163,28 +173,22 @@ Contribute at: https://github.com/goatsoft/Dalamud
private string creditsText;
private bool resetNow = false;
private GameFontHandle? thankYouFont;
/// <summary>
/// Initializes a new instance of the <see cref="CreditsWindow"/> class.
/// </summary>
public CreditsWindow()
: base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar, true)
public SettingsTabAbout()
{
var dalamud = Service<Dalamud>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"));
this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"))!;
this.creditsThrottler = new();
this.Size = new Vector2(500, 400);
this.SizeCondition = ImGuiCond.Always;
this.PositionCondition = ImGuiCond.Always;
this.BgAlpha = 0.8f;
}
public override SettingsEntry[] Entries { get; } = { };
public override string Title => Loc.Localize("DalamudAbout", "About");
/// <inheritdoc/>
public override void OnOpen()
{
@ -195,7 +199,10 @@ Contribute at: https://github.com/goatsoft/Dalamud
this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits, Util.GetGitHashClientStructs());
Service<GameGui>.Get().SetBgm(833);
var gg = Service<GameGui>.Get();
if (!gg.IsOnTitleScreen())
gg.SetBgm(833);
this.creditsThrottler.Restart();
if (this.thankYouFont == null)
@ -203,41 +210,36 @@ Contribute at: https://github.com/goatsoft/Dalamud
var gfm = Service<GameFontManager>.Get();
this.thankYouFont = gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.TrumpGothic34));
}
this.resetNow = true;
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true);
}
/// <inheritdoc/>
public override void OnClose()
{
this.creditsThrottler.Reset();
Service<GameGui>.Get().SetBgm(9999);
var gg = Service<GameGui>.Get();
if (!gg.IsOnTitleScreen())
gg.SetBgm(9999);
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(false);
}
/// <inheritdoc/>
public override void PreDraw()
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
base.PreDraw();
}
/// <inheritdoc/>
public override void PostDraw()
{
ImGui.PopStyleVar();
base.PostDraw();
}
/// <inheritdoc/>
public override void Draw()
{
var screenSize = ImGui.GetMainViewport().Size;
var windowSize = ImGui.GetWindowSize();
this.Position = (screenSize - windowSize) / 2;
ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar);
if (this.resetNow)
{
ImGui.SetScrollY(0);
this.resetNow = false;
}
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGuiHelpers.ScaledDummy(0, windowSize.Y + 20f);
@ -295,30 +297,13 @@ Contribute at: https://github.com/goatsoft/Dalamud
ImGui.EndChild();
ImGui.SetCursorPos(new Vector2(0));
ImGui.BeginChild("button", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar);
var closeButtonSize = new Vector2(30);
ImGui.PushFont(InterfaceManager.IconFont);
ImGui.SetCursorPos(new Vector2(windowSize.X - closeButtonSize.X - 5, 5));
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
if (ImGui.Button(FontAwesomeIcon.Times.ToIconString(), closeButtonSize))
{
this.IsOpen = false;
}
ImGui.PopStyleColor(3);
ImGui.PopFont();
ImGui.EndChild();
base.Draw();
}
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
public void Dispose()
public override void Dispose()
{
this.logoTexture?.Dispose();
this.thankYouFont?.Dispose();

View file

@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Gui.Dtr;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class SettingsTabDtr : SettingsTab
{
private List<string>? dtrOrder;
private List<string>? dtrIgnore;
private int dtrSpacing;
private bool dtrSwapDirection;
public override SettingsEntry[] Entries { get; } = Array.Empty<SettingsEntry>();
public override string Title => Loc.Localize("DalamudSettingsServerInfoBar", "Server Info Bar");
public override void Draw()
{
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarHint", "Plugins can put additional information into your server information bar(where world & time can be seen).\nYou can reorder and disable these here."));
ImGuiHelpers.ScaledDummy(10);
var configuration = Service<DalamudConfiguration>.Get();
var dtrBar = Service<DtrBar>.Get();
var order = configuration.DtrOrder!.Where(x => dtrBar.HasEntry(x)).ToList();
var ignore = configuration.DtrIgnore!.Where(x => dtrBar.HasEntry(x)).ToList();
var orderLeft = configuration.DtrOrder!.Where(x => !order.Contains(x)).ToList();
var ignoreLeft = configuration.DtrIgnore!.Where(x => !ignore.Contains(x)).ToList();
if (order.Count == 0)
{
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDidNone", "You have no plugins that use this feature."));
}
var isOrderChange = false;
for (var i = 0; i < order.Count; i++)
{
var title = order[i];
// TODO: Maybe we can also resort the rest of the bar in the future?
// var isRequired = search is Configuration.SearchSetting.Internal or Configuration.SearchSetting.MacroLinks;
ImGui.PushFont(UiBuilder.IconFont);
var arrowUpText = $"{FontAwesomeIcon.ArrowUp.ToIconString()}##{title}";
if (i == 0)
{
ImGuiComponents.DisabledButton(arrowUpText);
}
else
{
if (ImGui.Button(arrowUpText))
{
(order[i], order[i - 1]) = (order[i - 1], order[i]);
isOrderChange = true;
}
}
ImGui.SameLine();
var arrowDownText = $"{FontAwesomeIcon.ArrowDown.ToIconString()}##{title}";
if (i == order.Count - 1)
{
ImGuiComponents.DisabledButton(arrowDownText);
}
else
{
if (ImGui.Button(arrowDownText) && i != order.Count - 1)
{
(order[i], order[i + 1]) = (order[i + 1], order[i]);
isOrderChange = true;
}
}
ImGui.PopFont();
ImGui.SameLine();
// if (isRequired) {
// ImGui.TextUnformatted($"Search in {name}");
// } else {
var isShown = ignore.All(x => x != title);
var nextIsShow = isShown;
if (ImGui.Checkbox($"{title}###dtrEntry{i}", ref nextIsShow) && nextIsShow != isShown)
{
if (nextIsShow)
ignore.Remove(title);
else
ignore.Add(title);
dtrBar.MakeDirty(title);
}
// }
}
configuration.DtrOrder = order.Concat(orderLeft).ToList();
configuration.DtrIgnore = ignore.Concat(ignoreLeft).ToList();
if (isOrderChange)
dtrBar.ApplySort();
ImGuiHelpers.ScaledDummy(10);
ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarSpacing", "Server Info Bar spacing"));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarSpacingHint", "Configure the amount of space between entries in the server info bar here."));
ImGui.SliderInt("Spacing", ref this.dtrSpacing, 0, 40);
ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarDirection", "Server Info Bar direction"));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDirectionHint", "If checked, the Server Info Bar elements will expand to the right instead of the left."));
ImGui.Checkbox("Swap Direction", ref this.dtrSwapDirection);
base.Draw();
}
public override void OnClose()
{
var configuration = Service<DalamudConfiguration>.Get();
configuration.DtrOrder = this.dtrOrder;
configuration.DtrIgnore = this.dtrIgnore;
base.OnClose();
}
public override void Load()
{
var configuration = Service<DalamudConfiguration>.Get();
this.dtrSpacing = configuration.DtrSpacing;
this.dtrSwapDirection = configuration.DtrSwapDirection;
this.dtrOrder = configuration.DtrOrder;
this.dtrIgnore = configuration.DtrIgnore;
base.Load();
}
public override void Save()
{
var configuration = Service<DalamudConfiguration>.Get();
configuration.DtrSpacing = this.dtrSpacing;
configuration.DtrSwapDirection = this.dtrSwapDirection;
this.dtrOrder = configuration.DtrOrder;
this.dtrIgnore = configuration.DtrIgnore;
base.Save();
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Diagnostics.CodeAnalysis;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
using Dalamud.Interface.Internal.Windows.Settings.Widgets;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class SettingsTabExperimental : SettingsTab
{
public override SettingsEntry[] Entries { get; } =
{
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"),
string.Format(
Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for selected plugins.\nTo opt-in to testing builds for a plugin, you have to right click it in the \"{0}\" tab of the plugin installer and select \"{1}\"."),
PluginCategoryManager.Locs.Group_Installed,
PluginInstallerWindow.Locs.PluginContext_TestingOptIn),
c => c.DoPluginTest,
(v, c) => c.DoPluginTest = v),
new HintSettingsEntry(
Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may not have been vetted before being published. Please only enable this if you are aware of the risks."),
ImGuiColors.DalamudRed),
new GapSettingsEntry(5),
new ButtonSettingsEntry(
Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"),
Loc.Localize("DalamudSettingsClearHiddenHint", "Restore plugins you have previously hidden from the plugin installer."),
() =>
{
Service<DalamudConfiguration>.Get().HiddenPluginInternalName.Clear();
Service<PluginManager>.Get().RefilterPluginMasters();
}),
new GapSettingsEntry(5, true),
new DevPluginsSettingsEntry(),
new GapSettingsEntry(5, true),
new ThirdRepoSettingsEntry(),
};
public override string Title => Loc.Localize("DalamudSettingsExperimental", "Experimental");
public override void Draw()
{
base.Draw();
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, "Total memory used by Dalamud & Plugins: " + Util.FormatBytes(GC.GetTotalMemory(false)));
}
}

View file

@ -0,0 +1,92 @@
using System.Diagnostics.CodeAnalysis;
using CheapLoc;
using Dalamud.Game.Text;
using Dalamud.Interface.Internal.Windows.Settings.Widgets;
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class SettingsTabGeneral : SettingsTab
{
public override SettingsEntry[] Entries { get; } =
{
new LanguageChooserSettingsEntry(),
new GapSettingsEntry(5),
new SettingsEntry<XivChatType>(
Loc.Localize("DalamudSettingsChannel", "Dalamud Chat Channel"),
Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages."),
c => c.GeneralChatType,
(v, c) => c.GeneralChatType = v,
warning: (v) =>
{
// TODO: Maybe actually implement UI for the validity check...
if (v == XivChatType.None)
return Loc.Localize("DalamudSettingsChannelNone", "Do not pick \"None\".");
return null;
}),
new GapSettingsEntry(5),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsWaitForPluginsOnStartup", "Wait for plugins before game loads"),
Loc.Localize("DalamudSettingsWaitForPluginsOnStartupHint", "Do not let the game load, until plugins are loaded."),
c => c.IsResumeGameAfterPluginLoad,
(v, c) => c.IsResumeGameAfterPluginLoad = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"),
Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready."),
c => c.DutyFinderTaskbarFlash,
(v, c) => c.DutyFinderTaskbarFlash = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsDutyFinderMessage", "Chatlog message on duty pop"),
Loc.Localize("DalamudSettingsDutyFinderMessageHint", "Send a message in FFXIV chat when a duty is ready."),
c => c.DutyFinderChatMessage,
(v, c) => c.DutyFinderChatMessage = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsg", "Display loaded plugins in the welcome message"),
Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsgHint", "Display loaded plugins in FFXIV chat when logging in with a character."),
c => c.PrintPluginsWelcomeMsg,
(v, c) => c.PrintPluginsWelcomeMsg = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsAutoUpdatePlugins", "Auto-update plugins"),
Loc.Localize("DalamudSettingsAutoUpdatePluginsMsgHint", "Automatically update plugins when logging in with a character."),
c => c.AutoUpdatePlugins,
(v, c) => c.AutoUpdatePlugins = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"),
Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu."),
c => c.DoButtonsSystemMenu,
(v, c) => c.DoButtonsSystemMenu = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"),
Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu."),
c => c.DoButtonsSystemMenu,
(v, c) => c.DoButtonsSystemMenu = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsEnableRmtFiltering", "Enable RMT Filtering"),
Loc.Localize("DalamudSettingsEnableRmtFilteringMsgHint", "Enable Dalamud's built-in RMT ad filtering."),
c => !c.DisableRmtFiltering,
(v, c) => c.DisableRmtFiltering = !v),
new GapSettingsEntry(5),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingDoMbCollect", "Anonymously upload market board data"),
Loc.Localize("DalamudSettingDoMbCollectHint", "Anonymously provide data about in-game economics to Universalis when browsing the market board. This data can't be tied to you in any way and everyone benefits!"),
c => c.IsMbCollect,
(v, c) => c.IsMbCollect = v),
};
public override string Title => Loc.Localize("DalamudSettingsGeneral", "General");
}

View file

@ -0,0 +1,194 @@
using System.Diagnostics.CodeAnalysis;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.Windows.Settings.Widgets;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class SettingsTabLook : SettingsTab
{
private float globalUiScale;
private float fontGamma;
public override SettingsEntry[] Entries { get; } =
{
new GapSettingsEntry(5),
new ButtonSettingsEntry(
Loc.Localize("DalamudSettingsOpenStyleEditor", "Open Style Editor"),
Loc.Localize("DalamudSettingsStyleEditorHint", "Modify the look & feel of Dalamud windows."),
() => Service<DalamudInterface>.Get().OpenStyleEditor()),
new GapSettingsEntry(5),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleAxisFonts", "Use AXIS fonts as default Dalamud font"),
Loc.Localize("DalamudSettingToggleUiAxisFontsHint", "Use AXIS fonts (the game's main UI fonts) as default Dalamud font."),
c => c.UseAxisFontsFromGame,
(v, c) => c.UseAxisFontsFromGame = v,
v =>
{
var im = Service<InterfaceManager>.Get();
im.UseAxisOverride = v;
im.RebuildFonts();
}),
new GapSettingsEntry(5, true),
new HintSettingsEntry(Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below.")),
new GapSettingsEntry(3),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleUiHide", "Hide plugin UI when the game UI is toggled off"),
Loc.Localize("DalamudSettingToggleUiHideHint", "Hide any open windows by plugins when toggling the game overlay."),
c => c.ToggleUiHide,
(v, c) => c.ToggleUiHide = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleUiHideDuringCutscenes", "Hide plugin UI during cutscenes"),
Loc.Localize("DalamudSettingToggleUiHideDuringCutscenesHint", "Hide any open windows by plugins during cutscenes."),
c => c.ToggleUiHideDuringCutscenes,
(v, c) => c.ToggleUiHideDuringCutscenes = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleUiHideDuringGpose", "Hide plugin UI while gpose is active"),
Loc.Localize("DalamudSettingToggleUiHideDuringGposeHint", "Hide any open windows by plugins while gpose is active."),
c => c.ToggleUiHideDuringGpose,
(v, c) => c.ToggleUiHideDuringGpose = v),
new GapSettingsEntry(5, true),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleFocusManagement", "Use escape to close Dalamud windows"),
Loc.Localize("DalamudSettingToggleFocusManagementHint", "This will cause Dalamud windows to behave like in-game windows when pressing escape.\nThey will close one after another until all are closed. May not work for all plugins."),
c => c.IsFocusManagementEnabled,
(v, c) => c.IsFocusManagementEnabled = v),
// This is applied every frame in InterfaceManager::CheckViewportState()
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleViewports", "Enable multi-monitor windows"),
Loc.Localize("DalamudSettingToggleViewportsHint", "This will allow you move plugin windows onto other monitors.\nWill only work in Borderless Window or Windowed mode."),
c => !c.IsDisableViewport,
(v, c) => c.IsDisableViewport = !v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"),
Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows."),
c => c.IsDocking,
(v, c) => c.IsDocking = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleGamepadNavigation", "Control plugins via gamepad"),
Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and plugin navigation via L1+L3.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled."),
c => c.IsGamepadNavigationEnabled,
(v, c) => c.IsGamepadNavigationEnabled = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingToggleTsm", "Show title screen menu"),
Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen."),
c => c.ShowTsm,
(v, c) => c.ShowTsm = v),
};
public override string Title => Loc.Localize("DalamudSettingsVisual", "Look & Feel");
public override void Draw()
{
var interfaceManager = Service<InterfaceManager>.Get();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
if (ImGui.Button("9.6pt##DalamudSettingsGlobalUiScaleReset96"))
{
this.globalUiScale = 9.6f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12"))
{
this.globalUiScale = 1.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14"))
{
this.globalUiScale = 14.0f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18"))
{
this.globalUiScale = 18.0f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36"))
{
this.globalUiScale = 36.0f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
var globalUiScaleInPt = 12f * this.globalUiScale;
if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref globalUiScaleInPt, 0.1f, 9.6f, 36f, "%.1fpt", ImGuiSliderFlags.AlwaysClamp))
{
this.globalUiScale = globalUiScaleInPt / 12f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale text in all XIVLauncher UI elements - this is useful for 4K displays."));
ImGuiHelpers.ScaledDummy(5);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma"));
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset"))
{
this.fontGamma = 1.4f;
interfaceManager.FontGammaOverride = this.fontGamma;
interfaceManager.RebuildFonts();
}
if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, 0.3f, 3f, "%.2f", ImGuiSliderFlags.AlwaysClamp))
{
interfaceManager.FontGammaOverride = this.fontGamma;
interfaceManager.RebuildFonts();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFontGammaHint", "Changes the thickness of text."));
base.Draw();
}
public override void Load()
{
this.globalUiScale = Service<DalamudConfiguration>.Get().GlobalUiScale;
this.fontGamma = Service<DalamudConfiguration>.Get().FontGammaLevel;
base.Load();
}
public override void Save()
{
Service<DalamudConfiguration>.Get().GlobalUiScale = this.globalUiScale;
base.Save();
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Dalamud.Interface.Colors;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Widgets;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class ButtonSettingsEntry : SettingsEntry
{
private readonly string description;
private readonly Action runs;
public ButtonSettingsEntry(string name, string description, Action runs)
{
this.description = description;
this.runs = runs;
this.Name = name;
}
public override void Load()
{
// ignored
}
public override void Save()
{
// ignored
}
public override void Draw()
{
if (ImGui.Button(this.Name))
{
this.runs.Invoke();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, this.description);
}
}

View file

@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using CheapLoc;
using Dalamud.Configuration;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Plugin.Internal;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Widgets;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class DevPluginsSettingsEntry : SettingsEntry
{
private List<DevPluginLocationSettings> devPluginLocations = new();
private bool devPluginLocationsChanged;
private string devPluginTempLocation = string.Empty;
private string devPluginLocationAddError = string.Empty;
public DevPluginsSettingsEntry()
{
this.Name = Loc.Localize("DalamudSettingsDevPluginLocation", "Dev Plugin Locations");
}
public override void OnClose()
{
this.devPluginLocations =
Service<DalamudConfiguration>.Get().DevPluginLoadLocations.Select(x => x.Clone()).ToList();
}
public override void Load()
{
this.devPluginLocations =
Service<DalamudConfiguration>.Get().DevPluginLoadLocations.Select(x => x.Clone()).ToList();
this.devPluginLocationsChanged = false;
}
public override void Save()
{
Service<DalamudConfiguration>.Get().DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList();
if (this.devPluginLocationsChanged)
{
Service<PluginManager>.Get().ScanDevPlugins();
this.devPluginLocationsChanged = false;
}
}
public override void Draw()
{
ImGui.Text(this.Name);
if (this.devPluginLocationsChanged)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
ImGui.SameLine();
ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)"));
ImGui.PopStyleColor();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add additional dev plugin load locations.\nThese can be either the directory or DLL path."));
ImGuiHelpers.ScaledDummy(5);
ImGui.Columns(4);
ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale));
ImGui.Separator();
ImGui.Text("#");
ImGui.NextColumn();
ImGui.Text("Path");
ImGui.NextColumn();
ImGui.Text("Enabled");
ImGui.NextColumn();
ImGui.Text(string.Empty);
ImGui.NextColumn();
ImGui.Separator();
DevPluginLocationSettings locationToRemove = null;
var locNumber = 1;
foreach (var devPluginLocationSetting in this.devPluginLocations)
{
var isEnabled = devPluginLocationSetting.IsEnabled;
ImGui.PushID($"devPluginLocation_{devPluginLocationSetting.Path}");
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2));
ImGui.Text(locNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
var path = devPluginLocationSetting.Path;
if (ImGui.InputText($"##devPluginLocationInput", ref path, 65535, ImGuiInputTextFlags.EnterReturnsTrue))
{
var contains = this.devPluginLocations.Select(loc => loc.Path).Contains(path);
if (devPluginLocationSetting.Path == path)
{
// no change.
}
else if (contains && devPluginLocationSetting.Path != path)
{
this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists.");
Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty);
}
else
{
devPluginLocationSetting.Path = path;
this.devPluginLocationsChanged = path != devPluginLocationSetting.Path;
}
}
ImGui.NextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale));
ImGui.Checkbox("##devPluginLocationCheck", ref isEnabled);
ImGui.NextColumn();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
{
locationToRemove = devPluginLocationSetting;
}
ImGui.PopID();
ImGui.NextColumn();
ImGui.Separator();
devPluginLocationSetting.IsEnabled = isEnabled;
locNumber++;
}
if (locationToRemove != null)
{
this.devPluginLocations.Remove(locationToRemove);
this.devPluginLocationsChanged = true;
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2));
ImGui.Text(locNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
ImGui.InputText("##devPluginLocationInput", ref this.devPluginTempLocation, 300);
ImGui.NextColumn();
// Enabled button
ImGui.NextColumn();
if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{
if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase)))
{
this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists.");
Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty);
}
else
{
this.devPluginLocations.Add(new DevPluginLocationSettings
{
Path = this.devPluginTempLocation.Replace("\"", string.Empty),
IsEnabled = true,
});
this.devPluginLocationsChanged = true;
this.devPluginTempLocation = string.Empty;
}
}
ImGui.Columns(1);
if (!string.IsNullOrEmpty(this.devPluginLocationAddError))
{
ImGuiHelpers.SafeTextColoredWrapped(new Vector4(1, 0, 0, 1), this.devPluginLocationAddError);
}
}
}

View file

@ -0,0 +1,40 @@
using System.Diagnostics.CodeAnalysis;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Widgets;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public sealed class GapSettingsEntry : SettingsEntry
{
private readonly float size;
private readonly bool hr;
public GapSettingsEntry(float size, bool hr = false)
{
this.size = size;
this.hr = hr;
this.IsValid = true;
}
public override void Load()
{
// ignored
}
public override void Save()
{
// ignored
}
public override void Draw()
{
ImGuiHelpers.ScaledDummy(this.size);
if (this.hr)
{
ImGui.Separator();
ImGuiHelpers.ScaledDummy(this.size);
}
}
}

View file

@ -0,0 +1,34 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Dalamud.Interface.Colors;
namespace Dalamud.Interface.Internal.Windows.Settings.Widgets;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class HintSettingsEntry : SettingsEntry
{
private readonly string text;
private readonly Vector4 color;
public HintSettingsEntry(string text, Vector4? color = null)
{
this.text = text;
this.color = color ?? ImGuiColors.DalamudGrey;
}
public override void Load()
{
// ignore
}
public override void Save()
{
// ignore
}
public override void Draw()
{
ImGuiHelpers.SafeTextColoredWrapped(this.color, this.text);
}
}

View file

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Widgets;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public sealed class LanguageChooserSettingsEntry : SettingsEntry
{
private readonly string[] languages;
private readonly string[] locLanguages;
private int langIndex = -1;
public LanguageChooserSettingsEntry()
{
this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
this.Name = Loc.Localize("DalamudSettingsLanguage", "Language");
this.IsValid = true;
this.IsVisible = true;
try
{
var locLanguagesList = new List<string>();
string locLanguage;
foreach (var language in this.languages)
{
if (language != "ko")
{
locLanguage = CultureInfo.GetCultureInfo(language).NativeName;
locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage[1..]);
}
else
{
locLanguagesList.Add("Korean");
}
}
this.locLanguages = locLanguagesList.ToArray();
}
catch (Exception)
{
this.locLanguages = this.languages; // Languages not localized, only codes.
}
}
public override void Load()
{
this.langIndex = Array.IndexOf(this.languages, Service<DalamudConfiguration>.Get().EffectiveLanguage);
if (this.langIndex == -1)
this.langIndex = 0;
}
public override void Save()
{
Service<Localization>.Get().SetupWithLangCode(this.languages[this.langIndex]);
Service<DalamudConfiguration>.Get().LanguageOverride = this.languages[this.langIndex];
}
public override void Draw()
{
ImGui.Text(this.Name);
ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in."));
}
}

View file

@ -0,0 +1,174 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Widgets;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
internal sealed class SettingsEntry<T> : SettingsEntry
{
private readonly LoadSettingDelegate load;
private readonly SaveSettingDelegate save;
private readonly Action<T?>? change;
private object? valueBacking;
public SettingsEntry(string name, string description, LoadSettingDelegate load, SaveSettingDelegate save, Action<T?>? change = null, Func<T?, string?>? warning = null, Func<T?, string?>? validity = null)
{
this.load = load;
this.save = save;
this.change = change;
this.Name = name;
this.Description = description;
this.CheckWarning = warning;
this.CheckValidity = validity;
}
public delegate T? LoadSettingDelegate(DalamudConfiguration config);
public delegate void SaveSettingDelegate(T? value, DalamudConfiguration config);
public T? Value => this.valueBacking == default ? default : (T)this.valueBacking;
public string Description { get; }
public Func<T?, string?>? CheckValidity { get; init; }
public Func<T?, string?>? CheckWarning { get; init; }
public Func<bool>? CheckVisibility { get; init; }
public override bool IsVisible => this.CheckVisibility?.Invoke() ?? true;
public override void Draw()
{
Debug.Assert(this.Name != null, "this.Name != null");
var type = typeof(T);
if (type == typeof(DirectoryInfo))
{
ImGuiHelpers.SafeTextWrapped(this.Name);
var value = this.Value as DirectoryInfo;
var nativeBuffer = value?.FullName ?? string.Empty;
if (ImGui.InputText($"###{this.Id.ToString()}", ref nativeBuffer, 1000))
{
this.valueBacking = !string.IsNullOrEmpty(nativeBuffer) ? new DirectoryInfo(nativeBuffer) : null;
}
}
else if (type == typeof(string))
{
ImGuiHelpers.SafeTextWrapped(this.Name);
var nativeBuffer = this.Value as string ?? string.Empty;
if (ImGui.InputText($"###{this.Id.ToString()}", ref nativeBuffer, 1000))
{
this.valueBacking = nativeBuffer;
}
}
else if (type == typeof(bool))
{
var nativeValue = this.Value as bool? ?? false;
if (ImGui.Checkbox($"{this.Name}###{this.Id.ToString()}", ref nativeValue))
{
this.valueBacking = nativeValue;
this.change?.Invoke(this.Value);
}
}
else if (type.IsEnum)
{
ImGuiHelpers.SafeTextWrapped(this.Name);
var idx = (Enum)(this.valueBacking ?? 0);
var values = Enum.GetValues(type);
var descriptions =
values.Cast<Enum>().ToDictionary(x => x, x => x.GetAttribute<SettingsAnnotationAttribute>() ?? new SettingsAnnotationAttribute(x.ToString(), string.Empty));
if (ImGui.BeginCombo($"###{this.Id.ToString()}", descriptions[idx].FriendlyName))
{
foreach (Enum value in values)
{
if (ImGui.Selectable(descriptions[value].FriendlyName, idx.Equals(value)))
{
this.valueBacking = value;
}
}
ImGui.EndCombo();
}
}
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
ImGuiHelpers.SafeTextWrapped(this.Description);
ImGui.PopStyleColor();
if (this.CheckValidity != null)
{
var validityMsg = this.CheckValidity.Invoke(this.Value);
this.IsValid = string.IsNullOrEmpty(validityMsg);
if (!this.IsValid)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.Text(validityMsg);
ImGui.PopStyleColor();
}
}
else
{
this.IsValid = true;
}
var warningMessage = this.CheckWarning?.Invoke(this.Value);
if (warningMessage != null)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.Text(warningMessage);
ImGui.PopStyleColor();
}
}
public override void Load()
{
this.valueBacking = this.load(Service<DalamudConfiguration>.Get());
if (this.CheckValidity != null)
{
this.IsValid = this.CheckValidity(this.Value) == null;
}
else
{
this.IsValid = true;
}
}
public override void Save() => this.save(this.Value, Service<DalamudConfiguration>.Get());
}
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
[AttributeUsage(AttributeTargets.Field)]
internal class SettingsAnnotationAttribute : Attribute
{
public SettingsAnnotationAttribute(string friendlyName, string description)
{
this.FriendlyName = friendlyName;
this.Description = description;
}
public string FriendlyName { get; set; }
public string Description { get; set; }
}

View file

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using CheapLoc;
using Dalamud.Configuration;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Plugin.Internal;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.Settings.Widgets;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class ThirdRepoSettingsEntry : SettingsEntry
{
private List<ThirdPartyRepoSettings> thirdRepoList = new();
private bool thirdRepoListChanged;
private string thirdRepoTempUrl = string.Empty;
private string thirdRepoAddError = string.Empty;
public override void OnClose()
{
this.thirdRepoList =
Service<DalamudConfiguration>.Get().ThirdRepoList.Select(x => x.Clone()).ToList();
}
public override void Load()
{
this.thirdRepoList =
Service<DalamudConfiguration>.Get().ThirdRepoList.Select(x => x.Clone()).ToList();
this.thirdRepoListChanged = false;
}
public override void Save()
{
Service<DalamudConfiguration>.Get().ThirdRepoList =
this.thirdRepoList.Select(x => x.Clone()).ToList();
if (this.thirdRepoListChanged)
{
_ = Service<PluginManager>.Get().SetPluginReposFromConfigAsync(true);
this.thirdRepoListChanged = false;
}
}
public override void Draw()
{
ImGui.Text(Loc.Localize("DalamudSettingsCustomRepo", "Custom Plugin Repositories"));
if (this.thirdRepoListChanged)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
ImGui.SameLine();
ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)"));
ImGui.PopStyleColor();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingCustomRepoHint", "Add custom plugin repositories."));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning", "We cannot take any responsibility for third-party plugins and repositories."));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning2", "Plugins have full control over your PC, like any other program, and may cause harm or crashes."));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning4", "They can delete your character, upload your family photos and burn down your house."));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning3", "Please make absolutely sure that you only install third-party plugins from developers you trust."));
ImGuiHelpers.ScaledDummy(5);
ImGui.Columns(4);
ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale));
ImGui.Separator();
ImGui.Text("#");
ImGui.NextColumn();
ImGui.Text("URL");
ImGui.NextColumn();
ImGui.Text("Enabled");
ImGui.NextColumn();
ImGui.Text(string.Empty);
ImGui.NextColumn();
ImGui.Separator();
ImGui.Text("0");
ImGui.NextColumn();
ImGui.Text("XIVLauncher");
ImGui.NextColumn();
ImGui.NextColumn();
ImGui.NextColumn();
ImGui.Separator();
ThirdPartyRepoSettings repoToRemove = null;
var repoNumber = 1;
foreach (var thirdRepoSetting in this.thirdRepoList)
{
var isEnabled = thirdRepoSetting.IsEnabled;
ImGui.PushID($"thirdRepo_{thirdRepoSetting.Url}");
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2));
ImGui.Text(repoNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
var url = thirdRepoSetting.Url;
if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue))
{
var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url);
if (thirdRepoSetting.Url == url)
{
// no change.
}
else if (contains && thirdRepoSetting.Url != url)
{
this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists.");
Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty);
}
else
{
thirdRepoSetting.Url = url;
this.thirdRepoListChanged = url != thirdRepoSetting.Url;
}
}
ImGui.NextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale));
if (ImGui.Checkbox("##thirdRepoCheck", ref isEnabled))
{
this.thirdRepoListChanged = true;
}
ImGui.NextColumn();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
{
repoToRemove = thirdRepoSetting;
}
ImGui.PopID();
ImGui.NextColumn();
ImGui.Separator();
thirdRepoSetting.IsEnabled = isEnabled;
repoNumber++;
}
if (repoToRemove != null)
{
this.thirdRepoList.Remove(repoToRemove);
this.thirdRepoListChanged = true;
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2));
ImGui.Text(repoNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
ImGui.InputText("##thirdRepoUrlInput", ref this.thirdRepoTempUrl, 300);
ImGui.NextColumn();
// Enabled button
ImGui.NextColumn();
if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{
this.thirdRepoTempUrl = this.thirdRepoTempUrl.TrimEnd();
if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase)))
{
this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists.");
Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty);
}
else
{
this.thirdRepoList.Add(new ThirdPartyRepoSettings
{
Url = this.thirdRepoTempUrl,
IsEnabled = true,
});
this.thirdRepoListChanged = true;
this.thirdRepoTempUrl = string.Empty;
}
}
ImGui.Columns(1);
if (!string.IsNullOrEmpty(this.thirdRepoAddError))
{
ImGuiHelpers.SafeTextColoredWrapped(new Vector4(1, 0, 0, 1), this.thirdRepoAddError);
}
}
}

View file

@ -1,982 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using CheapLoc;
using Dalamud.Configuration;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Gui.Dtr;
using Dalamud.Game.Text;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// The window that allows for general configuration of Dalamud itself.
/// </summary>
internal class SettingsWindow : Window
{
private readonly string[] languages;
private readonly string[] locLanguages;
private int langIndex;
private XivChatType dalamudMessagesChatType;
private bool doWaitForPluginsOnStartup;
private bool doCfTaskBarFlash;
private bool doCfChatMessage;
private bool doMbCollect;
private float globalUiScale;
private bool doUseAxisFontsFromGame;
private float fontGamma;
private bool doToggleUiHide;
private bool doToggleUiHideDuringCutscenes;
private bool doToggleUiHideDuringGpose;
private bool doDocking;
private bool doViewport;
private bool doGamepad;
private bool doFocus;
private bool doTsm;
private List<string>? dtrOrder;
private List<string>? dtrIgnore;
private int dtrSpacing;
private bool dtrSwapDirection;
private int? pluginWaitBeforeFree;
private List<ThirdPartyRepoSettings> thirdRepoList;
private bool thirdRepoListChanged;
private string thirdRepoTempUrl = string.Empty;
private string thirdRepoAddError = string.Empty;
private List<DevPluginLocationSettings> devPluginLocations;
private bool devPluginLocationsChanged;
private string devPluginTempLocation = string.Empty;
private string devPluginLocationAddError = string.Empty;
private bool printPluginsWelcomeMsg;
private bool autoUpdatePlugins;
private bool doButtonsSystemMenu;
private bool disableRmtFiltering;
#region Experimental
private bool doPluginTest;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="SettingsWindow"/> class.
/// </summary>
public SettingsWindow()
: base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse)
{
var configuration = Service<DalamudConfiguration>.Get();
this.Size = new Vector2(740, 550);
this.SizeCondition = ImGuiCond.FirstUseEver;
this.dalamudMessagesChatType = configuration.GeneralChatType;
this.doWaitForPluginsOnStartup = configuration.IsResumeGameAfterPluginLoad;
this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash;
this.doCfChatMessage = configuration.DutyFinderChatMessage;
this.doMbCollect = configuration.IsMbCollect;
this.globalUiScale = configuration.GlobalUiScale;
this.fontGamma = configuration.FontGammaLevel;
this.doUseAxisFontsFromGame = configuration.UseAxisFontsFromGame;
this.doToggleUiHide = configuration.ToggleUiHide;
this.doToggleUiHideDuringCutscenes = configuration.ToggleUiHideDuringCutscenes;
this.doToggleUiHideDuringGpose = configuration.ToggleUiHideDuringGpose;
this.doDocking = configuration.IsDocking;
this.doViewport = !configuration.IsDisableViewport;
this.doGamepad = configuration.IsGamepadNavigationEnabled;
this.doFocus = configuration.IsFocusManagementEnabled;
this.doTsm = configuration.ShowTsm;
this.dtrSpacing = configuration.DtrSpacing;
this.dtrSwapDirection = configuration.DtrSwapDirection;
this.pluginWaitBeforeFree = configuration.PluginWaitBeforeFree;
this.doPluginTest = configuration.DoPluginTest;
this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList();
this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList();
this.printPluginsWelcomeMsg = configuration.PrintPluginsWelcomeMsg;
this.autoUpdatePlugins = configuration.AutoUpdatePlugins;
this.doButtonsSystemMenu = configuration.DoButtonsSystemMenu;
this.disableRmtFiltering = configuration.DisableRmtFiltering;
this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
this.langIndex = Array.IndexOf(this.languages, configuration.EffectiveLanguage);
if (this.langIndex == -1)
this.langIndex = 0;
try
{
var locLanguagesList = new List<string>();
string locLanguage;
foreach (var language in this.languages)
{
if (language != "ko")
{
locLanguage = CultureInfo.GetCultureInfo(language).NativeName;
locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage[1..]);
}
else
{
locLanguagesList.Add("Korean");
}
}
this.locLanguages = locLanguagesList.ToArray();
}
catch (Exception)
{
this.locLanguages = this.languages; // Languages not localized, only codes.
}
}
/// <inheritdoc/>
public override void OnOpen()
{
this.thirdRepoListChanged = false;
this.devPluginLocationsChanged = false;
var configuration = Service<DalamudConfiguration>.Get();
this.dtrOrder = configuration.DtrOrder;
this.dtrIgnore = configuration.DtrIgnore;
}
/// <inheritdoc/>
public override void OnClose()
{
var configuration = Service<DalamudConfiguration>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
var rebuildFont = ImGui.GetIO().FontGlobalScale != configuration.GlobalUiScale
|| interfaceManager.FontGamma != configuration.FontGammaLevel
|| interfaceManager.UseAxis != configuration.UseAxisFontsFromGame;
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
interfaceManager.FontGammaOverride = null;
interfaceManager.UseAxisOverride = null;
this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList();
this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList();
configuration.DtrOrder = this.dtrOrder;
configuration.DtrIgnore = this.dtrIgnore;
if (rebuildFont)
interfaceManager.RebuildFonts();
}
/// <inheritdoc/>
public override void Draw()
{
var windowSize = ImGui.GetWindowSize();
ImGui.BeginChild("scrolling", new Vector2(windowSize.X - 5 - (5 * ImGuiHelpers.GlobalScale), windowSize.Y - 35 - (35 * ImGuiHelpers.GlobalScale)), false);
if (ImGui.BeginTabBar("SetTabBar"))
{
if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General") + "###settingsTabGeneral"))
{
this.DrawGeneralTab();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel") + "###settingsTabVisual"))
{
this.DrawLookAndFeelTab();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsServerInfoBar", "Server Info Bar") + "###settingsTabInfoBar"))
{
this.DrawServerInfoBarTab();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental") + "###settingsTabExperimental"))
{
this.DrawExperimentalTab();
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.EndChild();
this.DrawSaveCloseButtons();
}
private void DrawGeneralTab()
{
ImGui.Text(Loc.Localize("DalamudSettingsLanguage", "Language"));
ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in."));
ImGuiHelpers.ScaledDummy(5);
ImGui.Text(Loc.Localize("DalamudSettingsChannel", "General Chat Channel"));
if (ImGui.BeginCombo("##XlChatTypeCombo", this.dalamudMessagesChatType.ToString()))
{
foreach (var type in Enum.GetValues(typeof(XivChatType)).Cast<XivChatType>())
{
if (ImGui.Selectable(type.ToString(), type == this.dalamudMessagesChatType))
{
this.dalamudMessagesChatType = type;
}
}
ImGui.EndCombo();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages."));
ImGuiHelpers.ScaledDummy(5);
ImGui.Checkbox(Loc.Localize("DalamudSettingsWaitForPluginsOnStartup", "Wait for plugins before game loads"), ref this.doWaitForPluginsOnStartup);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsWaitForPluginsOnStartupHint", "Do not let the game load, until plugins are loaded."));
ImGui.Checkbox(Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"), ref this.doCfTaskBarFlash);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready."));
ImGui.Checkbox(Loc.Localize("DalamudSettingsDutyFinderMessage", "Chatlog message on duty pop"), ref this.doCfChatMessage);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDutyFinderMessageHint", "Send a message in FFXIV chat when a duty is ready."));
ImGui.Checkbox(Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsg", "Display loaded plugins in the welcome message"), ref this.printPluginsWelcomeMsg);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsgHint", "Display loaded plugins in FFXIV chat when logging in with a character."));
ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdatePlugins", "Auto-update plugins"), ref this.autoUpdatePlugins);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdatePluginsMsgHint", "Automatically update plugins when logging in with a character."));
ImGui.Checkbox(Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"), ref this.doButtonsSystemMenu);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu."));
ImGui.Checkbox(Loc.Localize("DalamudSettingsDisableRmtFiltering", "Disable RMT Filtering"), ref this.disableRmtFiltering);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable Dalamud's built-in RMT ad filtering."));
ImGuiHelpers.ScaledDummy(5);
ImGui.Checkbox(Loc.Localize("DalamudSettingDoMbCollect", "Anonymously upload market board data"), ref this.doMbCollect);
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
ImGui.TextWrapped(Loc.Localize("DalamudSettingDoMbCollectHint", "Anonymously provide data about in-game economics to Universalis when browsing the market board. This data can't be tied to you in any way and everyone benefits!"));
ImGui.PopStyleColor();
}
private void DrawLookAndFeelTab()
{
var interfaceManager = Service<InterfaceManager>.Get();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
if (ImGui.Button("9.6pt##DalamudSettingsGlobalUiScaleReset96"))
{
this.globalUiScale = 9.6f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12"))
{
this.globalUiScale = 1.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14"))
{
this.globalUiScale = 14.0f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18"))
{
this.globalUiScale = 18.0f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGui.SameLine();
if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36"))
{
this.globalUiScale = 36.0f / 12.0f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
var globalUiScaleInPt = 12f * this.globalUiScale;
if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref globalUiScaleInPt, 0.1f, 9.6f, 36f, "%.1fpt", ImGuiSliderFlags.AlwaysClamp))
{
this.globalUiScale = globalUiScaleInPt / 12f;
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
interfaceManager.RebuildFonts();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale text in all XIVLauncher UI elements - this is useful for 4K displays."));
ImGuiHelpers.ScaledDummy(10, 16);
if (ImGui.Button(Loc.Localize("DalamudSettingsOpenStyleEditor", "Open Style Editor")))
{
Service<DalamudInterface>.Get().OpenStyleEditor();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsStyleEditorHint", "Modify the look & feel of Dalamud windows."));
ImGuiHelpers.ScaledDummy(10);
if (ImGui.Checkbox(Loc.Localize("DalamudSettingToggleAxisFonts", "Use AXIS fonts as default Dalamud font"), ref this.doUseAxisFontsFromGame))
{
interfaceManager.UseAxisOverride = this.doUseAxisFontsFromGame;
interfaceManager.RebuildFonts();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiAxisFontsHint", "Use AXIS fonts (the game's main UI fonts) as default Dalamud font."));
ImGuiHelpers.ScaledDummy(10);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below."));
ImGuiHelpers.ScaledDummy(3);
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHide", "Hide plugin UI when the game UI is toggled off"), ref this.doToggleUiHide);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideHint", "Hide any open windows by plugins when toggling the game overlay."));
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringCutscenes", "Hide plugin UI during cutscenes"), ref this.doToggleUiHideDuringCutscenes);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringCutscenesHint", "Hide any open windows by plugins during cutscenes."));
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringGpose", "Hide plugin UI while gpose is active"), ref this.doToggleUiHideDuringGpose);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringGposeHint", "Hide any open windows by plugins while gpose is active."));
ImGuiHelpers.ScaledDummy(10, 16);
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleFocusManagement", "Use escape to close Dalamud windows"), ref this.doFocus);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleFocusManagementHint", "This will cause Dalamud windows to behave like in-game windows when pressing escape.\nThey will close one after another until all are closed. May not work for all plugins."));
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleViewports", "Enable multi-monitor windows"), ref this.doViewport);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleViewportsHint", "This will allow you move plugin windows onto other monitors.\nWill only work in Borderless Window or Windowed mode."));
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"), ref this.doDocking);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows."));
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Control plugins via gamepad"), ref this.doGamepad);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and plugin navigation via L1+L3.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled."));
ImGui.Checkbox(Loc.Localize("DalamudSettingToggleTsm", "Show title screen menu"), ref this.doTsm);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen."));
ImGuiHelpers.ScaledDummy(10, 16);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma"));
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset"))
{
this.fontGamma = 1.4f;
interfaceManager.FontGammaOverride = this.fontGamma;
interfaceManager.RebuildFonts();
}
if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, 0.3f, 3f, "%.2f", ImGuiSliderFlags.AlwaysClamp))
{
interfaceManager.FontGammaOverride = this.fontGamma;
interfaceManager.RebuildFonts();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFontGammaHint", "Changes the thickness of text."));
ImGuiHelpers.ScaledDummy(10, 16);
}
private void DrawServerInfoBarTab()
{
ImGui.Text(Loc.Localize("DalamudSettingServerInfoBar", "Server Info Bar configuration"));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarHint", "Plugins can put additional information into your server information bar(where world & time can be seen).\nYou can reorder and disable these here."));
ImGuiHelpers.ScaledDummy(10);
var configuration = Service<DalamudConfiguration>.Get();
var dtrBar = Service<DtrBar>.Get();
var order = configuration.DtrOrder!.Where(x => dtrBar.HasEntry(x)).ToList();
var ignore = configuration.DtrIgnore!.Where(x => dtrBar.HasEntry(x)).ToList();
var orderLeft = configuration.DtrOrder!.Where(x => !order.Contains(x)).ToList();
var ignoreLeft = configuration.DtrIgnore!.Where(x => !ignore.Contains(x)).ToList();
if (order.Count == 0)
{
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDidNone", "You have no plugins that use this feature."));
}
var isOrderChange = false;
for (var i = 0; i < order.Count; i++)
{
var title = order[i];
// TODO: Maybe we can also resort the rest of the bar in the future?
// var isRequired = search is Configuration.SearchSetting.Internal or Configuration.SearchSetting.MacroLinks;
ImGui.PushFont(UiBuilder.IconFont);
var arrowUpText = $"{FontAwesomeIcon.ArrowUp.ToIconString()}##{title}";
if (i == 0)
{
ImGuiComponents.DisabledButton(arrowUpText);
}
else
{
if (ImGui.Button(arrowUpText))
{
(order[i], order[i - 1]) = (order[i - 1], order[i]);
isOrderChange = true;
}
}
ImGui.SameLine();
var arrowDownText = $"{FontAwesomeIcon.ArrowDown.ToIconString()}##{title}";
if (i == order.Count - 1)
{
ImGuiComponents.DisabledButton(arrowDownText);
}
else
{
if (ImGui.Button(arrowDownText) && i != order.Count - 1)
{
(order[i], order[i + 1]) = (order[i + 1], order[i]);
isOrderChange = true;
}
}
ImGui.PopFont();
ImGui.SameLine();
// if (isRequired) {
// ImGui.TextUnformatted($"Search in {name}");
// } else {
var isShown = ignore.All(x => x != title);
var nextIsShow = isShown;
if (ImGui.Checkbox($"{title}###dtrEntry{i}", ref nextIsShow) && nextIsShow != isShown)
{
if (nextIsShow)
ignore.Remove(title);
else
ignore.Add(title);
dtrBar.MakeDirty(title);
}
// }
}
configuration.DtrOrder = order.Concat(orderLeft).ToList();
configuration.DtrIgnore = ignore.Concat(ignoreLeft).ToList();
if (isOrderChange)
dtrBar.ApplySort();
ImGuiHelpers.ScaledDummy(10);
ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarSpacing", "Server Info Bar spacing"));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarSpacingHint", "Configure the amount of space between entries in the server info bar here."));
ImGui.SliderInt("Spacing", ref this.dtrSpacing, 0, 40);
ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarDirection", "Server Info Bar direction"));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDirectionHint", "If checked, the Server Info Bar elements will expand to the right instead of the left."));
ImGui.Checkbox("Swap Direction", ref this.dtrSwapDirection);
}
private void DrawExperimentalTab()
{
var configuration = Service<DalamudConfiguration>.Get();
var pluginManager = Service<PluginManager>.Get();
var useCustomPluginWaitBeforeFree = this.pluginWaitBeforeFree.HasValue;
if (ImGui.Checkbox(
Loc.Localize("DalamudSettingsPluginCustomizeWaitTime", "Customize wait time for plugin unload"),
ref useCustomPluginWaitBeforeFree))
{
if (!useCustomPluginWaitBeforeFree)
this.pluginWaitBeforeFree = null;
else
this.pluginWaitBeforeFree = PluginManager.PluginWaitBeforeFreeDefault;
}
if (useCustomPluginWaitBeforeFree)
{
var waitTime = this.pluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault;
if (ImGui.SliderInt(
"Wait time###DalamudSettingsPluginCustomizeWaitTimeSlider",
ref waitTime,
0,
5000))
{
this.pluginWaitBeforeFree = waitTime;
}
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginCustomizeWaitTimeHint", "Configure the wait time between stopping plugin and completely unloading plugin. If you are experiencing crashes when exiting the game, try increasing this value."));
ImGuiHelpers.ScaledDummy(12);
#region Plugin testing
ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest);
ImGuiHelpers.SafeTextColoredWrapped(
ImGuiColors.DalamudGrey,
string.Format(
Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for selected plugins.\nTo opt-in to testing builds for a plugin, you have to right click it in the \"{0}\" tab of the plugin installer and select \"{1}\"."),
PluginCategoryManager.Locs.Group_Installed,
PluginInstallerWindow.Locs.PluginContext_TestingOptIn));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may not have been vetted before being published. Please only enable this if you are aware of the risks."));
#endregion
ImGuiHelpers.ScaledDummy(12);
#region Hidden plugins
if (ImGui.Button(Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins")))
{
configuration.HiddenPluginInternalName.Clear();
pluginManager.RefilterPluginMasters();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsClearHiddenHint", "Restore plugins you have previously hidden from the plugin installer."));
#endregion
ImGuiHelpers.ScaledDummy(12);
this.DrawCustomReposSection();
ImGuiHelpers.ScaledDummy(12);
this.DrawDevPluginLocationsSection();
ImGuiHelpers.ScaledDummy(12);
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, "Total memory used by Dalamud & Plugins: " + Util.FormatBytes(GC.GetTotalMemory(false)));
}
private void DrawCustomReposSection()
{
ImGui.Text(Loc.Localize("DalamudSettingsCustomRepo", "Custom Plugin Repositories"));
if (this.thirdRepoListChanged)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
ImGui.SameLine();
ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)"));
ImGui.PopStyleColor();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingCustomRepoHint", "Add custom plugin repositories."));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning", "We cannot take any responsibility for third-party plugins and repositories."));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning2", "Plugins have full control over your PC, like any other program, and may cause harm or crashes."));
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning3", "Please make absolutely sure that you only install third-party plugins from developers you trust."));
ImGuiHelpers.ScaledDummy(5);
ImGui.Columns(4);
ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale));
ImGui.Separator();
ImGui.Text("#");
ImGui.NextColumn();
ImGui.Text("URL");
ImGui.NextColumn();
ImGui.Text("Enabled");
ImGui.NextColumn();
ImGui.Text(string.Empty);
ImGui.NextColumn();
ImGui.Separator();
ImGui.Text("0");
ImGui.NextColumn();
ImGui.Text("XIVLauncher");
ImGui.NextColumn();
ImGui.NextColumn();
ImGui.NextColumn();
ImGui.Separator();
ThirdPartyRepoSettings repoToRemove = null;
var repoNumber = 1;
foreach (var thirdRepoSetting in this.thirdRepoList)
{
var isEnabled = thirdRepoSetting.IsEnabled;
ImGui.PushID($"thirdRepo_{thirdRepoSetting.Url}");
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2));
ImGui.Text(repoNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
var url = thirdRepoSetting.Url;
if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue))
{
var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url);
if (thirdRepoSetting.Url == url)
{
// no change.
}
else if (contains && thirdRepoSetting.Url != url)
{
this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists.");
Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty);
}
else
{
thirdRepoSetting.Url = url;
this.thirdRepoListChanged = url != thirdRepoSetting.Url;
}
}
ImGui.NextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale));
ImGui.Checkbox("##thirdRepoCheck", ref isEnabled);
ImGui.NextColumn();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
{
repoToRemove = thirdRepoSetting;
}
ImGui.PopID();
ImGui.NextColumn();
ImGui.Separator();
thirdRepoSetting.IsEnabled = isEnabled;
repoNumber++;
}
if (repoToRemove != null)
{
this.thirdRepoList.Remove(repoToRemove);
this.thirdRepoListChanged = true;
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2));
ImGui.Text(repoNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
ImGui.InputText("##thirdRepoUrlInput", ref this.thirdRepoTempUrl, 300);
ImGui.NextColumn();
// Enabled button
ImGui.NextColumn();
if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{
this.thirdRepoTempUrl = this.thirdRepoTempUrl.TrimEnd();
if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase)))
{
this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists.");
Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty);
}
else
{
this.thirdRepoList.Add(new ThirdPartyRepoSettings
{
Url = this.thirdRepoTempUrl,
IsEnabled = true,
});
this.thirdRepoListChanged = true;
this.thirdRepoTempUrl = string.Empty;
}
}
ImGui.Columns(1);
if (!string.IsNullOrEmpty(this.thirdRepoAddError))
{
ImGuiHelpers.SafeTextColoredWrapped(new Vector4(1, 0, 0, 1), this.thirdRepoAddError);
}
}
private void DrawDevPluginLocationsSection()
{
ImGui.Text(Loc.Localize("DalamudSettingsDevPluginLocation", "Dev Plugin Locations"));
if (this.devPluginLocationsChanged)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
ImGui.SameLine();
ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)"));
ImGui.PopStyleColor();
}
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add additional dev plugin load locations.\nThese can be either the directory or DLL path."));
ImGuiHelpers.ScaledDummy(5);
ImGui.Columns(4);
ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale));
ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale));
ImGui.Separator();
ImGui.Text("#");
ImGui.NextColumn();
ImGui.Text("Path");
ImGui.NextColumn();
ImGui.Text("Enabled");
ImGui.NextColumn();
ImGui.Text(string.Empty);
ImGui.NextColumn();
ImGui.Separator();
DevPluginLocationSettings locationToRemove = null;
var locNumber = 1;
foreach (var devPluginLocationSetting in this.devPluginLocations)
{
var isEnabled = devPluginLocationSetting.IsEnabled;
ImGui.PushID($"devPluginLocation_{devPluginLocationSetting.Path}");
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2));
ImGui.Text(locNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
var path = devPluginLocationSetting.Path;
if (ImGui.InputText($"##devPluginLocationInput", ref path, 65535, ImGuiInputTextFlags.EnterReturnsTrue))
{
var contains = this.devPluginLocations.Select(loc => loc.Path).Contains(path);
if (devPluginLocationSetting.Path == path)
{
// no change.
}
else if (contains && devPluginLocationSetting.Path != path)
{
this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists.");
Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty);
}
else
{
devPluginLocationSetting.Path = path;
this.devPluginLocationsChanged = path != devPluginLocationSetting.Path;
}
}
ImGui.NextColumn();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale));
ImGui.Checkbox("##devPluginLocationCheck", ref isEnabled);
ImGui.NextColumn();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
{
locationToRemove = devPluginLocationSetting;
}
ImGui.PopID();
ImGui.NextColumn();
ImGui.Separator();
devPluginLocationSetting.IsEnabled = isEnabled;
locNumber++;
}
if (locationToRemove != null)
{
this.devPluginLocations.Remove(locationToRemove);
this.devPluginLocationsChanged = true;
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2));
ImGui.Text(locNumber.ToString());
ImGui.NextColumn();
ImGui.SetNextItemWidth(-1);
ImGui.InputText("##devPluginLocationInput", ref this.devPluginTempLocation, 300);
ImGui.NextColumn();
// Enabled button
ImGui.NextColumn();
if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{
if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase)))
{
this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists.");
Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty);
}
else
{
this.devPluginLocations.Add(new DevPluginLocationSettings
{
Path = this.devPluginTempLocation.Replace("\"", string.Empty),
IsEnabled = true,
});
this.devPluginLocationsChanged = true;
this.devPluginTempLocation = string.Empty;
}
}
ImGui.Columns(1);
if (!string.IsNullOrEmpty(this.devPluginLocationAddError))
{
ImGuiHelpers.SafeTextColoredWrapped(new Vector4(1, 0, 0, 1), this.devPluginLocationAddError);
}
}
private void DrawSaveCloseButtons()
{
var buttonSave = false;
var buttonClose = false;
var pluginManager = Service<PluginManager>.Get();
if (ImGui.Button(Loc.Localize("Save", "Save")))
buttonSave = true;
ImGui.SameLine();
if (ImGui.Button(Loc.Localize("Close", "Close")))
buttonClose = true;
ImGui.SameLine();
if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close")))
buttonSave = buttonClose = true;
if (buttonSave)
{
this.Save();
if (this.thirdRepoListChanged)
{
_ = pluginManager.SetPluginReposFromConfigAsync(true);
this.thirdRepoListChanged = false;
}
if (this.devPluginLocationsChanged)
{
pluginManager.ScanDevPlugins();
this.devPluginLocationsChanged = false;
}
}
if (buttonClose)
{
this.IsOpen = false;
}
}
private void Save()
{
var configuration = Service<DalamudConfiguration>.Get();
var localization = Service<Localization>.Get();
localization.SetupWithLangCode(this.languages[this.langIndex]);
configuration.LanguageOverride = this.languages[this.langIndex];
configuration.GeneralChatType = this.dalamudMessagesChatType;
configuration.IsResumeGameAfterPluginLoad = this.doWaitForPluginsOnStartup;
configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash;
configuration.DutyFinderChatMessage = this.doCfChatMessage;
configuration.IsMbCollect = this.doMbCollect;
configuration.GlobalUiScale = this.globalUiScale;
configuration.ToggleUiHide = this.doToggleUiHide;
configuration.ToggleUiHideDuringCutscenes = this.doToggleUiHideDuringCutscenes;
configuration.ToggleUiHideDuringGpose = this.doToggleUiHideDuringGpose;
configuration.IsDocking = this.doDocking;
configuration.IsGamepadNavigationEnabled = this.doGamepad;
configuration.IsFocusManagementEnabled = this.doFocus;
configuration.ShowTsm = this.doTsm;
configuration.UseAxisFontsFromGame = this.doUseAxisFontsFromGame;
configuration.FontGammaLevel = this.fontGamma;
// This is applied every frame in InterfaceManager::CheckViewportState()
configuration.IsDisableViewport = !this.doViewport;
// Apply docking flag
if (!configuration.IsDocking)
{
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable;
}
else
{
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;
}
// NOTE (Chiv) Toggle gamepad navigation via setting
if (!configuration.IsGamepadNavigationEnabled)
{
ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad;
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos;
var di = Service<DalamudInterface>.Get();
di.CloseGamepadModeNotifierWindow();
}
else
{
ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad;
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos;
}
this.dtrOrder = configuration.DtrOrder;
this.dtrIgnore = configuration.DtrIgnore;
configuration.DtrSpacing = this.dtrSpacing;
configuration.DtrSwapDirection = this.dtrSwapDirection;
configuration.PluginWaitBeforeFree = this.pluginWaitBeforeFree;
configuration.DoPluginTest = this.doPluginTest;
configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList();
configuration.DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList();
configuration.PrintPluginsWelcomeMsg = this.printPluginsWelcomeMsg;
configuration.AutoUpdatePlugins = this.autoUpdatePlugins;
configuration.DoButtonsSystemMenu = this.doButtonsSystemMenu;
configuration.DisableRmtFiltering = this.disableRmtFiltering;
configuration.QueueSave();
_ = Service<PluginManager>.Get().ReloadPluginMastersAsync();
Service<InterfaceManager>.Get().RebuildFonts();
}
}

View file

@ -134,7 +134,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
pos = finalPos;
}
this.DrawEntry(entry, moveEasing.IsRunning && i != 0, true, i == 0, true);
this.DrawEntry(entry, moveEasing.IsRunning && i != 0, true, i == 0, true, moveEasing.IsDone);
var cursor = ImGui.GetCursorPos();
cursor.Y = (float)pos;
@ -177,7 +177,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
var finalPos = (i + 1) * this.shadeTexture.Height * scale;
this.DrawEntry(entry, i != 0, true, i == 0, false);
this.DrawEntry(entry, i != 0, true, i == 0, false, false);
var cursor = ImGui.GetCursorPos();
cursor.Y = finalPos;
@ -205,7 +205,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
case State.Hide:
{
if (this.DrawEntry(tsm.Entries[0], true, false, true, true))
if (this.DrawEntry(tsm.Entries[0], true, false, true, true, false))
{
this.state = State.Show;
}
@ -228,7 +228,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
}
private bool DrawEntry(
TitleScreenMenu.TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha)
TitleScreenMenu.TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha, bool interactable)
{
InterfaceManager.SpecialGlyphRequest fontHandle;
if (this.specialGlyphRequests.TryGetValue(entry.Name, out fontHandle) && fontHandle.Size != TargetFontSizePx)
@ -271,7 +271,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
}
var isClick = ImGui.IsItemClicked();
if (isClick)
if (isClick && interactable)
{
entry.Trigger();
}

View file

@ -140,9 +140,10 @@ public class Localization : IServiceType
/// <summary>
/// Saves localizable JSON data in the current working directory for the provided assembly.
/// </summary>
public void ExportLocalizable()
/// <param name="ignoreInvalidFunctions">If set to true, this ignores malformed Localize functions instead of failing.</param>
public void ExportLocalizable(bool ignoreInvalidFunctions = false)
{
Loc.ExportLocalizableForAssembly(this.assembly);
Loc.ExportLocalizableForAssembly(this.assembly, ignoreInvalidFunctions);
}
private string ReadLocData(string langCode)

View file

@ -583,15 +583,18 @@ public static unsafe class MemoryHelper
/// <param name="value">The read in bytes.</param>
public static void ReadProcessMemory(IntPtr memoryAddress, ref byte[] value)
{
var length = value.Length;
var result = NativeFunctions.ReadProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, value, length, out _);
unchecked
{
var length = value.Length;
var result = NativeFunctions.ReadProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, value, length, out _);
if (!result)
throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})");
if (!result)
throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})");
}
}
/// <summary>
@ -602,15 +605,18 @@ public static unsafe class MemoryHelper
/// <param name="data">The bytes to write to memoryAddress.</param>
public static void WriteProcessMemory(IntPtr memoryAddress, byte[] data)
{
var length = data.Length;
var result = NativeFunctions.WriteProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, data, length, out _);
unchecked
{
var length = data.Length;
var result = NativeFunctions.WriteProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, data, length, out _);
if (!result)
throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})");
if (!result)
throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})");
}
}
#endregion

View file

@ -42,8 +42,8 @@ public sealed class DalamudPluginInterface : IDisposable
/// <param name="assemblyLocation">Location of the assembly.</param>
/// <param name="reason">The reason the plugin was loaded.</param>
/// <param name="isDev">A value indicating whether this is a dev plugin.</param>
/// <param name="sourceRepository">The repository from which the plugin is installed.</param>
internal DalamudPluginInterface(string pluginName, FileInfo assemblyLocation, PluginLoadReason reason, bool isDev, string sourceRepository)
/// <param name="manifest">The local manifest for this plugin.</param>
internal DalamudPluginInterface(string pluginName, FileInfo assemblyLocation, PluginLoadReason reason, bool isDev, LocalPluginManifest manifest)
{
var configuration = Service<DalamudConfiguration>.Get();
var dataManager = Service<DataManager>.Get();
@ -56,7 +56,8 @@ public sealed class DalamudPluginInterface : IDisposable
this.configs = Service<PluginManager>.Get().PluginConfigs;
this.Reason = reason;
this.IsDev = isDev;
this.SourceRepository = isDev ? LocalPluginManifest.FlagDevPlugin : sourceRepository;
this.SourceRepository = isDev ? LocalPluginManifest.FlagDevPlugin : manifest.InstalledFromUrl;
this.IsTesting = manifest.Testing;
this.LoadTime = DateTime.Now;
this.LoadTimeUTC = DateTime.UtcNow;
@ -97,7 +98,11 @@ public sealed class DalamudPluginInterface : IDisposable
public PluginLoadReason Reason { get; }
/// <summary>
/// Gets the custom repository from which this plugin is installed, <inheritdoc cref="LocalPluginManifest.FlagMainRepo"/>, or <inheritdoc cref="LocalPluginManifest.FlagDevPlugin"/>.
/// Gets the repository from which this plugin was installed.
///
/// If a plugin was installed from the official/main repository, this will return the value of
/// <see cref="LocalPluginManifest.FlagMainRepo"/>. Developer plugins will return the value of
/// <see cref="LocalPluginManifest.FlagDevPlugin"/>.
/// </summary>
public string SourceRepository { get; }
@ -106,6 +111,14 @@ public sealed class DalamudPluginInterface : IDisposable
/// </summary>
public bool IsDev { get; }
/// <summary>
/// Gets a value indicating whether this is a testing release of a plugin.
/// </summary>
/// <remarks>
/// Dev plugins have undefined behavior for this value, but can be expected to return <c>false</c>.
/// </remarks>
public bool IsTesting { get; }
/// <summary>
/// Gets the time that this plugin was loaded.
/// </summary>

View file

@ -38,12 +38,12 @@ internal partial class PluginManager : IDisposable, IServiceType
/// <summary>
/// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded.
/// </summary>
public const int DalamudApiLevel = 7;
public const int DalamudApiLevel = 8;
/// <summary>
/// Default time to wait between plugin unload and plugin assembly unload.
/// </summary>
public const int PluginWaitBeforeFreeDefault = 500;
public const int PluginWaitBeforeFreeDefault = 1000; // upped from 500ms, seems more stable
private const string DevPluginsDisclaimerFilename = "DONT_USE_THIS_FOLDER.txt";
@ -866,13 +866,13 @@ Thanks and have fun!";
{
try
{
if (!plugin.IsDisabled)
if (!plugin.IsDisabled && !plugin.IsOrphaned)
{
await plugin.LoadAsync(reason);
}
else
{
Log.Verbose($"{name} was disabled");
Log.Verbose($"{name} not loaded, disabled:{plugin.IsDisabled} orphaned:{plugin.IsOrphaned}");
}
}
catch (InvalidPluginException)

View file

@ -138,9 +138,9 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
return;
}
if (this.State != PluginState.Loaded && this.State != PluginState.LoadError)
if (this.State != PluginState.Loaded && this.State != PluginState.LoadError && this.State != PluginState.UnloadError)
{
Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded} nor {PluginState.LoadError}.");
Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}, {PluginState.LoadError} or {PluginState.UnloadError}.");
return;
}
@ -148,6 +148,12 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
try
{
if (this.State == PluginState.UnloadError)
{
Log.Warning($"{this.Manifest.Author}: TAKE CARE!!! You need to fix your unload error, and restart the game - your plugin might be in an inconsistent state.");
Log.Warning("Reloading anyway, as this is a dev plugin, but you might encounter unexpected results.");
}
await this.ReloadAsync();
notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success);
}

View file

@ -15,7 +15,6 @@ using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal.Exceptions;
using Dalamud.Plugin.Internal.Loader;
using Dalamud.Utility;
using Dalamud.Utility.Signatures;
namespace Dalamud.Plugin.Internal.Types;
@ -220,8 +219,13 @@ internal class LocalPlugin : IDisposable
/// </summary>
public bool IsOrphaned => !this.IsDev &&
!this.Manifest.InstalledFromUrl.IsNullOrEmpty() && // TODO(api8): Remove this, all plugins will have a proper flag
Service<PluginManager>.Get().Repos.All(x => x.PluginMasterUrl != this.Manifest.InstalledFromUrl) &&
this.Manifest.InstalledFromUrl != LocalPluginManifest.FlagMainRepo;
this.GetSourceRepository() == null;
/// <summary>
/// Gets a value indicating whether or not this plugin is serviced(repo still exists, but plugin no longer does).
/// </summary>
public bool IsDecommissioned => !this.IsDev &&
this.GetSourceRepository()?.PluginMaster?.FirstOrDefault(x => x.InternalName == this.Manifest.InternalName) == null;
/// <summary>
/// Gets a value indicating whether this plugin has been banned.
@ -300,8 +304,13 @@ internal class LocalPlugin : IDisposable
throw new InvalidPluginOperationException(
$"Unable to load {this.Name}, load previously faulted, unload first");
case PluginState.UnloadError:
throw new InvalidPluginOperationException(
if (!this.IsDev)
{
throw new InvalidPluginOperationException(
$"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
}
break;
case PluginState.Unloaded:
break;
case PluginState.Loading:
@ -397,11 +406,10 @@ internal class LocalPlugin : IDisposable
}
// Update the location for the Location and CodeBase patches
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] =
new PluginPatchData(this.DllFile);
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
this.DalamudInterface =
new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev, this.Manifest.InstalledFromUrl);
new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev, this.Manifest);
if (this.Manifest.LoadSync && this.Manifest.LoadRequiredState is 0 or 1)
{
@ -466,8 +474,13 @@ internal class LocalPlugin : IDisposable
case PluginState.Unloaded:
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
case PluginState.UnloadError:
throw new InvalidPluginOperationException(
$"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
if (!this.IsDev)
{
throw new InvalidPluginOperationException(
$"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
}
break;
case PluginState.Loaded:
case PluginState.LoadError:
break;
@ -526,7 +539,10 @@ internal class LocalPlugin : IDisposable
/// <returns>A task.</returns>
public async Task ReloadAsync()
{
await this.UnloadAsync(true);
// Don't unload if we're a dev plugin and have an unload error, this is a bad idea but whatever
if (this.IsDev && this.State != PluginState.UnloadError)
await this.UnloadAsync(true);
await this.LoadAsync(PluginLoadReason.Reload, true);
}
@ -553,8 +569,11 @@ internal class LocalPlugin : IDisposable
throw new ArgumentOutOfRangeException(this.State.ToString());
}
if (!this.Manifest.Disabled)
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled");
// NOTE(goat): This is inconsequential, and we do have situations where a plugin can end up enabled but not loaded:
// Orphaned plugins can have their repo added back, but may not have been loaded at boot and may still be enabled.
// We don't want to disable orphaned plugins when they are orphaned so this is how it's going to be.
// if (!this.Manifest.Disabled)
// throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled");
this.Manifest.Disabled = false;
this.Manifest.ScheduledForDeletion = false;
@ -638,6 +657,25 @@ internal class LocalPlugin : IDisposable
}
}
/// <summary>
/// Get the repository this plugin was installed from.
/// </summary>
/// <returns>The plugin repository this plugin was installed from, or null if it is no longer there or if the plugin is a dev plugin.</returns>
public PluginRepository? GetSourceRepository()
{
if (this.IsDev)
return null;
var repos = Service<PluginManager>.Get().Repos;
return repos.FirstOrDefault(x =>
{
if (!x.IsThirdParty && !this.Manifest.IsThirdParty)
return true;
return x.PluginMasterUrl == this.Manifest.InstalledFromUrl;
});
}
private static void SetupLoaderConfig(LoaderConfig config)
{
config.IsUnloadable = true;

View file

@ -68,8 +68,8 @@ internal static class ServiceManager
using (Timings.Start("CS Resolver Init"))
{
FFXIVClientStructs.Resolver.InitializeParallel(
new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json")));
FFXIVClientStructs.Interop.Resolver.GetInstance.SetupSearchSpace(Service<SigScanner>.Get().SearchBase, new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json")));
FFXIVClientStructs.Interop.Resolver.GetInstance.Resolve();
}
}

View file

@ -14,12 +14,15 @@ public static class EnumExtensions
/// <typeparam name="TAttribute">The type of attribute to get.</typeparam>
/// <param name="value">The enum value that has an attached attribute.</param>
/// <returns>The attached attribute, if any.</returns>
public static TAttribute GetAttribute<TAttribute>(this Enum value)
public static TAttribute? GetAttribute<TAttribute>(this Enum value)
where TAttribute : Attribute
{
var type = value.GetType();
var name = Enum.GetName(type, value);
return type.GetField(name) // I prefer to get attributes this way
if (name.IsNullOrEmpty())
return null;
return type.GetField(name)?
.GetCustomAttributes(false)
.OfType<TAttribute>()
.SingleOrDefault();

View file

@ -8,7 +8,6 @@ using Nuke.Common.ProjectModel;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.MSBuild;
[CheckBuildProjectConfigurations]
[UnsetVisualStudioEnvironmentVariables]
public class DalamudBuild : NukeBuild
{

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Nullable>disable</Nullable>
<RootNamespace></RootNamespace>
<NoWarn>IDE0002;IDE0051;IDE1006;CS0649;CS0169</NoWarn>
@ -10,6 +10,6 @@
<NukeTelemetryVersion>1</NukeTelemetryVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="6.0.1" />
<PackageReference Include="Nuke.Common" Version="6.2.1" />
</ItemGroup>
</Project>

View file

@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.0",
"version": "7.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}

View file

@ -51,6 +51,9 @@ int InitializeClrAndGetEntryPoint(
SetEnvironmentVariable(L"DOTNET_TC_QuickJitForLoops", L"1");
SetEnvironmentVariable(L"DOTNET_ReadyToRun", L"1");
// WINE does not support QUIC and we don't need it
SetEnvironmentVariable(L"DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3SUPPORT", L"0");
SetEnvironmentVariable(L"COMPlus_ETWEnabled", enable_etw ? L"1" : L"0");
wchar_t* dotnet_path;
@ -126,7 +129,7 @@ int InitializeClrAndGetEntryPoint(
// =========================================================================== //
logging::I("Loading module...");
logging::I("Loading module from {}...", module_path.c_str());
if ((result = g_clr->load_assembly_and_get_function_pointer(
module_path.c_str(),
entrypoint_assembly_name.c_str(),

@ -1 +1 @@
Subproject commit 9cb2eb33826f62f7e3525e1f6ca08a974b35c76b
Subproject commit e166ae024d04dc18b115405c8788cbcaf8d4c0f0