├── .gitmodules
├── .gitignore
├── global.json
├── .editorconfig
├── ChillFrames
├── Classes
│ ├── IFrameLimiterOption.cs
│ ├── FrameLimiterCondition.cs
│ └── Configuration.cs
├── LimiterOptions
│ ├── Gpose.cs
│ ├── Combat.cs
│ ├── BoundByDuty.cs
│ ├── Crafting.cs
│ ├── Cutscene.cs
│ ├── QuestEvent.cs
│ ├── Estate.cs
│ ├── BardPerformance.cs
│ ├── DutyRecorderPlayback.cs
│ └── IslandSanctuary.cs
├── Services.cs
├── System.cs
├── Utilities
│ ├── Config.cs
│ ├── ConditionExtensions.cs
│ └── FileHelpers.cs
├── ChillFrames.csproj
├── Controllers
│ ├── IpcController.cs
│ ├── DtrController.cs
│ └── FrameLimiterController.cs
├── packages.lock.json
├── ChillFramesPlugin.cs
└── Windows
│ └── SettingsWindow.cs
├── .idea
└── .idea.ChillFrames
│ └── .idea
│ ├── encodings.xml
│ ├── vcs.xml
│ ├── indexLayout.xml
│ └── .gitignore
├── LICENSE
├── .github
└── FUNDING.yml
├── ChillFrames.sln
├── .gitattributes
└── README.md
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | obj/
3 | bin/
4 | *.user
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "10.0.0",
4 | "rollForward": "latestMajor",
5 | "allowPrerelease": true
6 | }
7 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
4 | dotnet_diagnostic.CS8618.severity = suggestion
5 |
--------------------------------------------------------------------------------
/ChillFrames/Classes/IFrameLimiterOption.cs:
--------------------------------------------------------------------------------
1 | namespace ChillFrames.Classes;
2 |
3 | public interface IFrameLimiterOption {
4 | string Label { get; }
5 | ref bool Enabled { get; }
6 | bool Active { get; }
7 | }
--------------------------------------------------------------------------------
/.idea/.idea.ChillFrames/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/.idea.ChillFrames/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/.idea.ChillFrames/.idea/indexLayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/.idea.ChillFrames/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Rider ignored files
5 | /projectSettingsUpdater.xml
6 | /modules.xml
7 | /.idea.ChillFrames.iml
8 | /contentModel.xml
9 | # Editor-based HTTP Client requests
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/Gpose.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using FFXIVClientStructs.FFXIV.Client.Game;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class Gpose : IFrameLimiterOption {
7 | public string Label => "GPose";
8 |
9 | public bool Active => GameMain.IsInGPose();
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringGpose;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/Combat.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class Combat : IFrameLimiterOption {
7 | public string Label => "Combat";
8 |
9 | public bool Active => Services.Condition.IsInCombat;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringCombatSetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/BoundByDuty.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class BoundByDuty : IFrameLimiterOption {
7 | public string Label => "Duties";
8 |
9 | public bool Active => Services.Condition.IsBoundByDuty;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringDutySetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/Crafting.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class Crafting : IFrameLimiterOption {
7 | public string Label => "Crafting";
8 |
9 | public bool Active => Services.Condition.IsCrafting;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringCraftingSetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/Cutscene.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class Cutscene : IFrameLimiterOption {
7 | public string Label => "Cutscenes";
8 |
9 | public bool Active => Services.Condition.IsInCutscene;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringCutsceneSetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/QuestEvent.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class QuestEvent : IFrameLimiterOption {
7 | public string Label => "Quest Event";
8 |
9 | public bool Active => Services.Condition.IsInQuestEvent;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringQuestEventSetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/Estate.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using FFXIVClientStructs.FFXIV.Client.Game;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public unsafe class Estate : IFrameLimiterOption {
7 | public string Label => "Inside Estate";
8 |
9 | public bool Active => HousingManager.Instance()->IsInside();
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableInEstatesSetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/BardPerformance.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class BardPerformance : IFrameLimiterOption {
7 | public string Label => "Bard Performance";
8 |
9 | public bool Active => Services.Condition.IsInBardPerformance;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringBardPerformance;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/Classes/FrameLimiterCondition.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.Classes;
5 |
6 | internal static class FrameLimiterCondition {
7 | public static bool DisableFramerateLimit() {
8 | if (System.LimiterOptions.Any(option => option is { Enabled: true, Active: true })) return true;
9 | if (Services.Condition.IsBetweenAreas) return true;
10 |
11 | return false;
12 | }
13 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/DutyRecorderPlayback.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using ChillFrames.Utilities;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public class DutyRecorderPlayback : IFrameLimiterOption {
7 | public string Label => "Duty Recorder Playback";
8 |
9 | public bool Active => Services.Condition.IsDutyRecorderPlayback;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableDuringDutyRecorderPlaybackSetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/LimiterOptions/IslandSanctuary.cs:
--------------------------------------------------------------------------------
1 | using ChillFrames.Classes;
2 | using FFXIVClientStructs.FFXIV.Client.Game.MJI;
3 |
4 | namespace ChillFrames.LimiterOptions;
5 |
6 | public unsafe class IslandSanctuary : IFrameLimiterOption {
7 | public string Label => "Island Sanctuary";
8 |
9 | public bool Active => MJIManager.Instance() is not null && MJIManager.Instance()->IsPlayerInSanctuary;
10 |
11 | public ref bool Enabled => ref System.Config.General.DisableIslandSanctuarySetting;
12 | }
--------------------------------------------------------------------------------
/ChillFrames/Services.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.IoC;
2 | using Dalamud.Plugin;
3 | using Dalamud.Plugin.Services;
4 |
5 | namespace ChillFrames;
6 |
7 | public class Services {
8 | [PluginService] public static IDalamudPluginInterface PluginInterface { get; set; } = null!;
9 | [PluginService] public static IFramework Framework { get; set; } = null!;
10 | [PluginService] public static ICondition Condition { get; set; } = null!;
11 | [PluginService] public static IDtrBar DtrBar { get; set; } = null!;
12 | [PluginService] public static IPluginLog PluginLog { get; set; } = null!;
13 | [PluginService] public static ICommandManager CommandManager { get; set; } = null!;
14 | [PluginService] public static IChatGui ChatGui { get; set; } = null!;
15 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2022 MidoriKami
2 |
3 | This program is free software: you can redistribute it and/or modify
4 | it under the terms of the GNU Affero General Public License as
5 | published by the Free Software Foundation, either version 3 of the
6 | License, or (at your option) any later version.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
--------------------------------------------------------------------------------
/ChillFrames/System.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ChillFrames.Classes;
3 | using ChillFrames.Controllers;
4 | using ChillFrames.Windows;
5 | using Dalamud.Interface.Windowing;
6 |
7 | namespace ChillFrames;
8 |
9 | public static class System {
10 | public static Configuration Config { get; set; } = null!;
11 | public static WindowSystem WindowSystem { get; set; } = null!;
12 | public static SettingsWindow ConfigWindow { get; set; } = null!;
13 | public static DtrController DtrController { get; set; } = null!;
14 | public static HashSet BlockList { get; set; } = [];
15 | public static FrameLimiterController FrameLimiterController { get; set; } = null!;
16 | public static IpcController IpcController { get; set; } = null!;
17 | public static List LimiterOptions { get; set; } = [];
18 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: MidoriKami
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/ChillFrames/Utilities/Config.cs:
--------------------------------------------------------------------------------
1 | namespace ChillFrames.Utilities;
2 |
3 | ///
4 | /// Configuration File Utilities
5 | ///
6 | public static class Config {
7 | public static string ConfigPath => FileHelpers.GetFileInfo().FullName;
8 |
9 | ///
10 | /// Loads a configuration file from PluginConfigs\ChillFrames\{FileName}
11 | /// Creates a `new T()` if the file can't be loaded
12 | ///
13 | public static T LoadConfig(string fileName) where T : new()
14 | => FileHelpers.LoadFile(FileHelpers.GetFileInfo(fileName).FullName);
15 |
16 | ///
17 | /// Saves a configuration file to PluginConfigs\ChillFrames\{FileName}
18 | ///
19 | public static void SaveConfig(T modificationConfig, string fileName)
20 | => FileHelpers.SaveFile(modificationConfig, FileHelpers.GetFileInfo(fileName).FullName);
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/ChillFrames/ChillFrames.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | 3.2.0.1
4 |
5 |
6 |
7 | MidoriKami
8 | Chill Frames
9 | ChillFrames
10 | Dynamic Framerate Limiter, changes fps limit depend on what you are doing
11 | Tired of your GPU melting your face off while just running around? Why render at 240 FPS when you're just staring at that gathering node? Fret no more! Chill Frames allows you dynamically limit your framerate when doing unimportant tasks!
12 | https://github.com/MidoriKami/ChillFrames
13 | fps;chill;limiter;dtr
14 | false
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ChillFrames.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChillFrames", "ChillFrames\ChillFrames.csproj", "{ED012F22-F994-4178-809F-7CDC44BFF840}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Release|x64 = Release|x64
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {ED012F22-F994-4178-809F-7CDC44BFF840}.Debug|x64.ActiveCfg = Debug|x64
15 | {ED012F22-F994-4178-809F-7CDC44BFF840}.Debug|x64.Build.0 = Debug|x64
16 | {ED012F22-F994-4178-809F-7CDC44BFF840}.Release|x64.ActiveCfg = Release|x64
17 | {ED012F22-F994-4178-809F-7CDC44BFF840}.Release|x64.Build.0 = Release|x64
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/ChillFrames/Utilities/ConditionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.Conditions;
2 | using Dalamud.Plugin.Services;
3 | using FFXIVClientStructs.FFXIV.Client.Game.MJI;
4 |
5 | namespace ChillFrames.Utilities;
6 |
7 | public static unsafe class ConditionExtensions {
8 | extension(ICondition condition) {
9 | public bool IsBoundByDuty => condition.Any(ConditionFlag.BoundByDuty, ConditionFlag.BoundByDuty56, ConditionFlag.BoundByDuty95);
10 | public bool IsInCombat => condition.Any(ConditionFlag.InCombat);
11 | public bool IsInCutscene => condition.Any(ConditionFlag.OccupiedInCutSceneEvent, ConditionFlag.WatchingCutscene, ConditionFlag.WatchingCutscene78);
12 | public bool IsBetweenAreas => condition.Any(ConditionFlag.BetweenAreas, ConditionFlag.BetweenAreas51);
13 | public bool IsCrafting => condition.Any(ConditionFlag.Crafting, ConditionFlag.ExecutingCraftingAction, ConditionFlag.PreparingToCraft);
14 | public bool IsInBardPerformance => condition.Any(ConditionFlag.Performing);
15 | public bool IsInQuestEvent => condition.Any(ConditionFlag.OccupiedInQuestEvent) || IsIslandDoingSomethingMode;
16 | public bool IsDutyRecorderPlayback => condition.Any(ConditionFlag.DutyRecorderPlayback);
17 | }
18 |
19 | public static bool IsIslandDoingSomethingMode => MJIManager.Instance()->CurrentMode is not 0 && MJIManager.Instance()->IsPlayerInSanctuary;
20 | }
21 |
--------------------------------------------------------------------------------
/ChillFrames/Controllers/IpcController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Dalamud.Plugin.Ipc;
3 |
4 | namespace ChillFrames.Controllers;
5 |
6 | public class IpcController : IDisposable {
7 | ///
8 | /// Function Signature: bool DisableLimiter(string callingPluginName)
9 | /// Locks out ChillFrame's frame limiter, this results in the framerate being unmodified entirely by ChillFrames.
10 | ///
11 | private static ICallGateProvider? disableLimiter;
12 |
13 | ///
14 | /// Function Signature: bool EnableLimiter(string callingPluginName)
15 | /// Removes the lockout placed by callingPluginName.
16 | ///
17 | private static ICallGateProvider? enableLimiter;
18 |
19 | public IpcController() {
20 | disableLimiter = Services.PluginInterface.GetIpcProvider("ChillFrames.DisableLimiter");
21 | enableLimiter = Services.PluginInterface.GetIpcProvider("ChillFrames.EnableLimiter");
22 |
23 | disableLimiter.RegisterFunc(DisableLimiter);
24 | enableLimiter.RegisterFunc(EnableLimiter);
25 | }
26 |
27 | public void Dispose() {
28 | disableLimiter?.UnregisterFunc();
29 | enableLimiter?.UnregisterFunc();
30 | }
31 |
32 | private bool DisableLimiter(string callingPluginName)
33 | => System.BlockList.Add(callingPluginName);
34 |
35 | private bool EnableLimiter(string callingPluginName)
36 | => System.BlockList.Remove(callingPluginName);
37 | }
--------------------------------------------------------------------------------
/ChillFrames/Classes/Configuration.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Numerics;
3 | using ChillFrames.Utilities;
4 | using Dalamud.Interface;
5 |
6 | namespace ChillFrames.Classes;
7 |
8 | public class GeneralSettings {
9 | public bool DisableDuringBardPerformance = true;
10 | public bool DisableDuringCombatSetting = true;
11 | public bool DisableDuringCraftingSetting = true;
12 | public bool DisableDuringCutsceneSetting = true;
13 | public bool DisableDuringDutyRecorderPlaybackSetting = true;
14 | public bool DisableDuringDutySetting = true;
15 | public bool DisableDuringGpose = true;
16 | public bool DisableDuringQuestEventSetting = true;
17 | public bool DisableIslandSanctuarySetting = true;
18 | public bool DisableInEstatesSetting = true;
19 | public bool EnableDtrBar = true;
20 | public bool EnableDtrColor = true;
21 | public Vector4 ActiveColor = KnownColor.LightGreen.Vector();
22 | public Vector4 InactiveColor = KnownColor.OrangeRed.Vector();
23 | }
24 |
25 | public class LimiterSettings {
26 | public int ActiveFramerateTarget = 60;
27 | public int IdleFramerateTarget = 60;
28 | }
29 |
30 | public class Configuration {
31 | public float DisableIncrementSetting = 0.025f;
32 | public float EnableIncrementSetting = 0.01f;
33 |
34 | public GeneralSettings General = new();
35 | public LimiterSettings Limiter = new();
36 |
37 | public bool PluginEnable = true;
38 |
39 | public static Configuration Load()
40 | => Config.LoadConfig("System.config.json");
41 |
42 | public void Save()
43 | => Config.SaveConfig(this, "System.config.json");
44 | }
--------------------------------------------------------------------------------
/ChillFrames/Controllers/DtrController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Dalamud.Game.ClientState.Conditions;
3 | using Dalamud.Game.Gui.Dtr;
4 | using Dalamud.Game.Text.SeStringHandling;
5 | using Dalamud.Utility;
6 | using SeStringBuilder = Lumina.Text.SeStringBuilder;
7 |
8 | namespace ChillFrames.Controllers;
9 |
10 | public class DtrController : IDisposable {
11 | private readonly IDtrBarEntry dtrEntry;
12 |
13 | public DtrController() {
14 | dtrEntry = Services.DtrBar.Get("Chill Frames");
15 |
16 | dtrEntry.Tooltip = GetTooltip();
17 | dtrEntry.OnClick = DtrOnClick;
18 |
19 | dtrEntry.Shown = System.Config.General.EnableDtrBar;
20 | }
21 |
22 | public void Dispose()
23 | => dtrEntry.Remove();
24 |
25 | private void DtrOnClick(DtrInteractionEvent dtrInteractionEvent) {
26 | switch (dtrInteractionEvent.ClickType) {
27 | case MouseClickType.Left:
28 | if (Services.Condition.Any(ConditionFlag.InCombat)) return;
29 | System.Config.PluginEnable = !System.Config.PluginEnable;
30 | System.Config.Save();
31 | break;
32 |
33 | case MouseClickType.Right:
34 | System.ConfigWindow.Toggle();
35 | break;
36 | }
37 |
38 | dtrEntry.Tooltip = GetTooltip();
39 | }
40 |
41 | public void Update() {
42 | if (System.Config.General.EnableDtrColor) {
43 | dtrEntry.Text = new SeStringBuilder()
44 | .PushColorRgba(System.Config.PluginEnable ? System.Config.General.ActiveColor : System.Config.General.InactiveColor)
45 | .Append($"{1000 / FrameLimiterController.LastFrametime.TotalMilliseconds:N0} FPS")
46 | .PopColor()
47 | .ToReadOnlySeString()
48 | .ToDalamudString();
49 | }
50 | else {
51 | dtrEntry.Text = $"{1000 / FrameLimiterController.LastFrametime.TotalMilliseconds:N0} FPS";
52 | }
53 | }
54 |
55 | private SeString GetTooltip()
56 | => $"{(System.Config.PluginEnable ? "Left Click to Disable Limiter" : "Left Click to Enable Limiter")}\n" +
57 | $"Right click to open Config Window";
58 |
59 | public void UpdateEnabled()
60 | => dtrEntry.Shown = System.Config.General.EnableDtrBar;
61 | }
--------------------------------------------------------------------------------
/ChillFrames/packages.lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "dependencies": {
4 | "net10.0-windows7.0": {
5 | "DalamudPackager": {
6 | "type": "Direct",
7 | "requested": "[14.0.1, )",
8 | "resolved": "14.0.1",
9 | "contentHash": "y0WWyUE6dhpGdolK3iKgwys05/nZaVf4ZPtIjpLhJBZvHxkkiE23zYRo7K7uqAgoK/QvK5cqF6l3VG5AbgC6KA=="
10 | },
11 | "DotNet.ReproducibleBuilds": {
12 | "type": "Direct",
13 | "requested": "[1.2.39, )",
14 | "resolved": "1.2.39",
15 | "contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
16 | },
17 | "HtmlAgilityPack": {
18 | "type": "Transitive",
19 | "resolved": "1.11.46",
20 | "contentHash": "dLMn4EVfJBHWmWK4Uh0XGD76FPLHI0qr2Tm0s1m/xmgiHb1JUb9zB8AzO8HtrkBBlMN6JfCUBYddhqC0hZNR+g=="
21 | },
22 | "Karashiiro.HtmlAgilityPack.CssSelectors.NetCoreFork": {
23 | "type": "Transitive",
24 | "resolved": "0.0.2",
25 | "contentHash": "+7cqe/+tN7gDW36pgrefSeyAQvhqznM34D0uTwnETgU25iAC6boPtblxbrMluW5rA9o6MzjEg4qZK7WrnT+tSw==",
26 | "dependencies": {
27 | "HtmlAgilityPack": "1.11.46"
28 | }
29 | },
30 | "Microsoft.Extensions.ObjectPool": {
31 | "type": "Transitive",
32 | "resolved": "10.0.0-preview.6.25358.103",
33 | "contentHash": "r49n6FoNDeCC0G/KPLHHxg/VneYlpRNVmpzWyAiKAJ3wfpSOsbXB84Owyt3loATmO5aP20th0xGmKavdgDItDA=="
34 | },
35 | "NetStone": {
36 | "type": "Transitive",
37 | "resolved": "1.1.1",
38 | "contentHash": "7AWc3j6082Ut3xTbJTWBhsZC2Zd7hYVprQiNiei+FGxSSvGacv+xQMBpBIfAnQnnmsKnEQ47NExNgZpt6UcI4A==",
39 | "dependencies": {
40 | "HtmlAgilityPack": "1.11.46",
41 | "Karashiiro.HtmlAgilityPack.CssSelectors.NetCoreFork": "0.0.2",
42 | "Newtonsoft.Json": "13.0.3"
43 | }
44 | },
45 | "Newtonsoft.Json": {
46 | "type": "Transitive",
47 | "resolved": "13.0.3",
48 | "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
49 | },
50 | "kamilib": {
51 | "type": "Project",
52 | "dependencies": {
53 | "Microsoft.Extensions.ObjectPool": "[10.0.0-preview.6.25358.103, )",
54 | "NetStone": "[1.1.1, )"
55 | }
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/ChillFrames/Utilities/FileHelpers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text.Json;
4 | using Dalamud.Utility;
5 |
6 | namespace ChillFrames.Utilities;
7 |
8 | public static class FileHelpers {
9 | private static readonly JsonSerializerOptions SerializerOptions = new() {
10 | WriteIndented = true,
11 | IncludeFields = true,
12 | };
13 |
14 | public static T LoadFile(string filePath) where T : new() {
15 | var fileInfo = new FileInfo(filePath);
16 | if (fileInfo is { Exists: true }) {
17 | try {
18 | var fileText = File.ReadAllText(fileInfo.FullName);
19 | var dataObject = JsonSerializer.Deserialize(fileText, SerializerOptions);
20 |
21 | // If deserialize result is null, create a new instance instead and save it.
22 | if (dataObject is null) {
23 | dataObject = new T();
24 | SaveFile(dataObject, filePath);
25 | }
26 |
27 | return dataObject;
28 | }
29 | catch (Exception e) {
30 | // If there is any kind of error loading the file, generate a new one instead and save it.
31 | Services.PluginLog.Error(e, $"Error trying to load file {filePath}, creating a new one instead.");
32 |
33 | SaveFile(new T(), filePath);
34 | }
35 | }
36 |
37 | var newFile = new T();
38 | SaveFile(newFile, filePath);
39 |
40 | return newFile;
41 | }
42 |
43 | public static void SaveFile(T? file, string filePath) {
44 | try {
45 | if (file is null) {
46 | Services.PluginLog.Error("Null file provided.");
47 | return;
48 | }
49 |
50 | var fileText = JsonSerializer.Serialize(file, file.GetType(), SerializerOptions);
51 | FilesystemUtil.WriteAllTextSafe(filePath, fileText);
52 | }
53 | catch (Exception e) {
54 | Services.PluginLog.Error(e, $"Error trying to save file {filePath}");
55 | }
56 | }
57 |
58 | public static FileInfo GetFileInfo(params string[] path) {
59 | var directory = Services.PluginInterface.ConfigDirectory;
60 |
61 | for (var index = 0; index < path.Length - 1; index++) {
62 | directory = new DirectoryInfo(Path.Combine(directory.FullName, path[index]));
63 | if (!directory.Exists) {
64 | directory.Create();
65 | }
66 | }
67 |
68 | return new FileInfo(Path.Combine(directory.FullName, path[^1]));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChillFrames
2 | [](https://github.com/MidoriKami/ChillFrames)
3 |
4 | Chill Frames is a XivLauncher/Dalamud plugin.
5 |
6 | This is a very simple utility plugin that allows you to conditionally enable a framerate limiter in-game.
7 |
8 | ## Why is this useful?
9 |
10 | This plugin allows you set a frame limiter for the parts of the game that aren't that important to have constantly running at a high framerate.
11 | When your framerate isn't limited your GPU is using maximum power, generating maximum heat, for minimal visual impact.
12 |
13 | For those that game on a mobile device this will also allow you significantly increase your battery life.
14 |
15 | ## How does it work?
16 |
17 | This plugin uses the games built in framework update system to correctly limit your framerate to the desired value.
18 |
19 | If the time between the last frame and the current frame was too short,
20 | then a "sleep" is introduced that suspends the calculation that the game performs each frame.
21 |
22 | This results in the framerate being reduced to the correct values, while conserving battery power/energy.
23 |
24 | ChillFrames can only *reduce* the games framerate,
25 | if you use the games setting to reduce the framerate while afk,
26 | that setting will still apply and the lower of the two limits will be used.
27 |
28 | ## Dalamud Multi-Monitor Support
29 |
30 | When you enable Dalamud's Multi-Monitor Support mode that allows you to have plugin windows on other monitors,
31 | the game splits the "viewport" used for rendering,
32 | this causes most graphics card driver-based framerate limiters to incorrectly calculate how to limit the framerate.
33 |
34 | ChillFrames mitigates this problem by using the games framework system itself to determine when a frame has ended,
35 | allowing you to maintain the correct framerate while using Multi-Monitor Support mode.
36 |
37 | ## Configuration Window
38 |
39 | ChillFrames lets you set a specific upper limit, and a specific lower limit, you can set what limit ChillFrames uses in the configuration window.
40 |
41 | The various options allow you to turn off the frame limiter (that is, to have maximum fps) under specific conditions.
42 |
43 | For example, if you want maximum fps while crafting you would set the Crafting condition to "Use Upper Limit ( 120 fps )" in this example.
44 |
45 | 
46 |
47 | ## Server Info Bar Entry (DTR Entry)
48 |
49 | ChillFrames also offers a **precise framerate display** that shows the exact fps you are currently getting.
50 |
51 | This display does not average your fps at all intentionally, it is very useful to see frame stuttering issues or inconsistent frametimes.
52 |
53 | Clicking on the Server Info Bar Entry will toggle enable/disable the plugins framelimiter,
54 | without needing to use a chat command, or open the configuration window to temporarly disable the limiter.
55 |
56 | 
57 |
58 | ## Zone Blacklist
59 |
60 | ChillFrames allows you to specify specific zones that you want to disable the limiter entirely while in those zones.
61 |
62 | 
63 |
64 |
65 | ## Testimonials
66 |
67 | 
68 |
--------------------------------------------------------------------------------
/ChillFrames/Controllers/FrameLimiterController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading;
5 | using ChillFrames.Classes;
6 | using Dalamud.Plugin.Services;
7 |
8 | namespace ChillFrames.Controllers;
9 |
10 | public enum LimiterState {
11 | Enabled,
12 | Disabled,
13 | SteadyState,
14 | }
15 |
16 | public class FrameLimiterController : IDisposable {
17 | private readonly Stopwatch steppingStopwatch = Stopwatch.StartNew();
18 | private readonly Stopwatch timer = Stopwatch.StartNew();
19 | private float delayRatio = 1.0f;
20 | private bool enabledLastFrame;
21 |
22 | private LimiterState state;
23 |
24 | private static LimiterSettings Settings => System.Config.Limiter;
25 |
26 | private static int TargetIdleFramerate => Settings.IdleFramerateTarget;
27 | private static int TargetIdleFrametime => 1000 / TargetIdleFramerate;
28 | private static int PreciseIdleFrametime => (int) (1000.0f / TargetIdleFramerate * 10000);
29 |
30 | private static int TargetActiveFramerate => Settings.ActiveFramerateTarget;
31 | private static int TargetActiveFrametime => 1000 / TargetActiveFramerate;
32 | private static int PreciseActiveFrametime => (int) (1000.0f / TargetActiveFramerate * 10000);
33 |
34 | private static float DisableIncrement => System.Config.DisableIncrementSetting;
35 | private static float EnableIncrement => System.Config.EnableIncrementSetting;
36 |
37 | public static TimeSpan LastFrametime;
38 |
39 | public FrameLimiterController() {
40 | Services.Framework.Update += OnFrameworkUpdate;
41 | }
42 |
43 | public void Dispose() {
44 | Services.Framework.Update -= OnFrameworkUpdate;
45 | }
46 |
47 | private void OnFrameworkUpdate(IFramework framework) {
48 | UpdateState();
49 |
50 | UpdateRate();
51 |
52 | TryLimitFramerate();
53 |
54 | LastFrametime = timer.Elapsed;
55 | timer.Restart();
56 |
57 | if (System.Config.General.EnableDtrBar) {
58 | System.DtrController.Update();
59 | }
60 | }
61 |
62 | [MethodImpl(MethodImplOptions.NoOptimization)]
63 | private void TryLimitFramerate() {
64 | if (!System.Config.PluginEnable) return;
65 | if (System.BlockList.Count > 0) return;
66 |
67 | if (!FrameLimiterCondition.DisableFramerateLimit() || state != LimiterState.SteadyState) {
68 | PerformLimiting(TargetIdleFrametime, PreciseIdleFrametime);
69 | }
70 | else if (FrameLimiterCondition.DisableFramerateLimit() || state != LimiterState.SteadyState) {
71 | PerformLimiting(TargetActiveFrametime, PreciseActiveFrametime);
72 | }
73 | }
74 |
75 | private void PerformLimiting(int targetFrametime, int preciseFrameTickTime) {
76 | var delayTime = (int) (targetFrametime - timer.ElapsedMilliseconds);
77 |
78 | if (delayTime - 1 > 0) {
79 | Thread.Sleep(delayTime - 1);
80 | }
81 |
82 | while (timer.ElapsedTicks <= preciseFrameTickTime) {
83 | ((Action) (() => { }))();
84 | }
85 | }
86 |
87 | private void UpdateState() {
88 | var shouldLimit = !FrameLimiterCondition.DisableFramerateLimit();
89 |
90 | if (enabledLastFrame != shouldLimit) {
91 | state = enabledLastFrame switch {
92 | true => LimiterState.Disabled,
93 | false => LimiterState.Enabled
94 | };
95 | }
96 |
97 | enabledLastFrame = shouldLimit;
98 | }
99 |
100 | private void UpdateRate() {
101 | const int stepDelay = 40;
102 |
103 | if (steppingStopwatch.ElapsedMilliseconds > stepDelay) {
104 | switch (state) {
105 | case LimiterState.Enabled when delayRatio < 1.0f:
106 | delayRatio += EnableIncrement;
107 | break;
108 |
109 | case LimiterState.Enabled when delayRatio >= 1.0f:
110 | state = LimiterState.SteadyState;
111 | delayRatio = 1.0f;
112 | break;
113 |
114 | case LimiterState.Disabled when delayRatio > 0.0f:
115 | delayRatio -= DisableIncrement;
116 | break;
117 |
118 | case LimiterState.Disabled when delayRatio <= 0.0f:
119 | delayRatio = 0.0f;
120 | state = LimiterState.SteadyState;
121 | break;
122 |
123 | case LimiterState.SteadyState:
124 | break;
125 | }
126 |
127 | steppingStopwatch.Restart();
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/ChillFrames/ChillFramesPlugin.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using ChillFrames.Classes;
6 | using ChillFrames.Controllers;
7 | using ChillFrames.Utilities;
8 | using ChillFrames.Windows;
9 | using Dalamud.Game.Command;
10 | using Dalamud.Interface.Windowing;
11 | using Dalamud.Plugin;
12 |
13 | namespace ChillFrames;
14 |
15 | public sealed class ChillFramesPlugin : IDalamudPlugin {
16 | public ChillFramesPlugin(IDalamudPluginInterface pluginInterface) {
17 | pluginInterface.Create();
18 |
19 | // We need to disable these, so users can monitor the config window and see what conditions are active at what times.
20 | pluginInterface.UiBuilder.DisableCutsceneUiHide = true;
21 | pluginInterface.UiBuilder.DisableAutomaticUiHide = true;
22 | pluginInterface.UiBuilder.DisableGposeUiHide = true;
23 | pluginInterface.UiBuilder.DisableUserUiHide = true;
24 |
25 | System.LimiterOptions = GetFrameLimiterOptions();
26 |
27 | System.Config = Configuration.Load();
28 |
29 | System.DtrController = new DtrController();
30 | System.FrameLimiterController = new FrameLimiterController();
31 | System.IpcController = new IpcController();
32 | Services.CommandManager.AddHandler("/chillframes", new CommandInfo(OnCommand) {
33 | ShowInHelp = true, HelpMessage = "Open ChillFrames Config",
34 | });
35 |
36 | Services.CommandManager.AddHandler("/pcf", new CommandInfo(OnCommand) {
37 | ShowInHelp = true, HelpMessage = "Open ChillFrames Config",
38 | });
39 |
40 | System.WindowSystem = new WindowSystem("ChillFrames");
41 | System.ConfigWindow = new SettingsWindow();
42 |
43 | System.WindowSystem.AddWindow(System.ConfigWindow);
44 |
45 | Services.PluginInterface.UiBuilder.Draw += System.WindowSystem.Draw;
46 | Services.PluginInterface.UiBuilder.OpenConfigUi += System.ConfigWindow.Toggle;
47 | Services.PluginInterface.UiBuilder.OpenMainUi += System.ConfigWindow.Toggle;
48 | }
49 |
50 | private void OnCommand(string command, string arguments) {
51 | if (Services.Condition.IsInCombat) {
52 | Services.ChatGui.PrintError("Unable to modify ChillFrames config while in combat.");
53 | return;
54 | }
55 |
56 | if (command is not ( "/chillframes" or "/pcf" )) return;
57 |
58 | switch (arguments.Split(' ')) {
59 | case [ "" ] or []:
60 | System.ConfigWindow.Toggle();
61 | break;
62 |
63 | case [ "enable" ]:
64 | System.Config.PluginEnable = true;
65 | break;
66 |
67 | case [ "disable" ]:
68 | System.Config.PluginEnable = false;
69 | break;
70 |
71 | case [ "toggle" ]:
72 | System.Config.PluginEnable = !System.Config.PluginEnable;
73 | break;
74 |
75 | case [ "fps", "setlower", { } newLowerLimit ]:
76 | if (!int.TryParse(newLowerLimit, out var newLowTarget) || newLowTarget <= 0) return;
77 | System.Config.Limiter.IdleFramerateTarget = newLowTarget;
78 | break;
79 |
80 | case [ "fps", "setupper", { } newUpperLimit ]:
81 | if (!int.TryParse(newUpperLimit, out var newHighTarget) || newHighTarget <= 0) return;
82 | System.Config.Limiter.ActiveFramerateTarget = newHighTarget;
83 | break;
84 | }
85 |
86 | System.Config.Save();
87 | }
88 |
89 | public void Dispose() {
90 | Services.PluginInterface.UiBuilder.Draw -= System.WindowSystem.Draw;
91 | Services.PluginInterface.UiBuilder.OpenConfigUi -= System.ConfigWindow.Toggle;
92 | Services.PluginInterface.UiBuilder.OpenMainUi -= System.ConfigWindow.Toggle;
93 |
94 | Services.CommandManager.RemoveHandler("/chillframes");
95 | Services.CommandManager.RemoveHandler("/pcf");
96 |
97 | System.FrameLimiterController.Dispose();
98 | System.IpcController.Dispose();
99 | System.WindowSystem.RemoveAllWindows();
100 | }
101 |
102 | private static List GetFrameLimiterOptions() => Assembly
103 | .GetCallingAssembly()
104 | .GetTypes()
105 | .Where(type => type.IsAssignableTo(typeof(IFrameLimiterOption)))
106 | .Where(type => !type.IsAbstract)
107 | .Select(type => (IFrameLimiterOption?) Activator.CreateInstance(type))
108 | .OfType()
109 | .ToList();
110 | }
--------------------------------------------------------------------------------
/ChillFrames/Windows/SettingsWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.Numerics;
4 | using ChillFrames.Classes;
5 | using ChillFrames.Controllers;
6 | using Dalamud.Bindings.ImGui;
7 | using Dalamud.Game.ClientState.Conditions;
8 | using Dalamud.Interface;
9 | using Dalamud.Interface.Components;
10 | using Dalamud.Interface.Utility;
11 | using Dalamud.Interface.Utility.Raii;
12 | using Dalamud.Interface.Windowing;
13 |
14 | namespace ChillFrames.Windows;
15 |
16 | public class SettingsWindow : Window {
17 | private int idleFramerateLimitTemp = int.MinValue;
18 | private int activeFramerateLimitTemp = int.MinValue;
19 | private static Configuration Config => System.Config;
20 |
21 | public SettingsWindow() : base("ChillFrames Settings") {
22 | SizeConstraints = new WindowSizeConstraints {
23 | MinimumSize = new Vector2(500.0f, 500.0f),
24 | };
25 |
26 | Flags |= ImGuiWindowFlags.NoScrollbar;
27 | Flags |= ImGuiWindowFlags.NoScrollWithMouse;
28 | }
29 |
30 | public override void PreDraw() {
31 | if (idleFramerateLimitTemp is int.MinValue) {
32 | idleFramerateLimitTemp = System.Config.Limiter.IdleFramerateTarget;
33 | }
34 |
35 | if (activeFramerateLimitTemp is int.MinValue) {
36 | activeFramerateLimitTemp = System.Config.Limiter.ActiveFramerateTarget;
37 | }
38 | }
39 |
40 | public override void Draw() {
41 | using var uiLockout = ImRaii.Disabled(Services.Condition.Any(ConditionFlag.InCombat));
42 | DrawLimiterStatus();
43 |
44 | using var tabBar = ImRaii.TabBar("ChillFramesSettingsTabBar");
45 | if (!tabBar) return;
46 |
47 | using (var settingsTab = ImRaii.TabItem("Limiter Settings")) {
48 | if (settingsTab) {
49 | DrawSettings();
50 | }
51 | }
52 |
53 | using (var dtrSettings = ImRaii.TabItem("DTR Entry")) {
54 | if (dtrSettings) {
55 | DrawDtrSettings();
56 | }
57 | }
58 | }
59 |
60 | private void DrawLimiterStatus() {
61 | using var statusTable = ImRaii.Table("status_table", 2);
62 | if (!statusTable) return;
63 |
64 | ImGui.TableNextColumn();
65 | ImGui.Text($"Current Framerate");
66 |
67 | ImGui.TableNextColumn();
68 | ImGui.Text($"{1000 / FrameLimiterController.LastFrametime.TotalMilliseconds:F} fps");
69 |
70 | ImGui.TableNextColumn();
71 | if (System.BlockList.Count > 0) {
72 | if (ImGuiComponents.IconButton("##ReleaseLocks", FontAwesomeIcon.Unlock)) {
73 | System.BlockList.Clear();
74 | }
75 | if (ImGui.IsItemHovered()) {
76 | ImGui.SetTooltip("Remove limiter lock");
77 | }
78 |
79 | ImGui.SameLine();
80 | ImGui.TextColoredWrapped(KnownColor.Red.Vector(), $"Limiter is inactive - requested by plugin(s): {string.Join(", ", System.BlockList)}");
81 | ImGui.TableNextColumn();
82 | }
83 | else if (!FrameLimiterCondition.DisableFramerateLimit() && Config.PluginEnable) {
84 | ImGui.Text($"Target Framerate");
85 | ImGui.TableNextColumn();
86 | ImGui.Text($"{Config.Limiter.IdleFramerateTarget} fps");
87 | }
88 | else if (FrameLimiterCondition.DisableFramerateLimit() && Config.PluginEnable) {
89 | ImGui.Text($"Target Framerate");
90 | ImGui.TableNextColumn();
91 | ImGui.Text($"{Config.Limiter.ActiveFramerateTarget} fps");
92 | }
93 | else {
94 | ImGui.TextColored(KnownColor.Red.Vector(), "Limiter Inactive");
95 | ImGui.TableNextColumn();
96 | }
97 | }
98 |
99 | private void DrawSettings() {
100 | ImGuiHelpers.ScaledDummy(5.0f);
101 | DrawFpsLimitOptions();
102 |
103 | ImGuiHelpers.ScaledDummy(5.0f);
104 | DrawLimiterOptions();
105 | }
106 |
107 | private void DrawDtrSettings() {
108 | ImGuiHelpers.ScaledDummy(10.0f);
109 | ImGui.Text("Feature Toggles");
110 | ImGui.Separator();
111 | ImGuiHelpers.ScaledDummy(5.0f);
112 | DrawFeatureToggles();
113 |
114 | ImGuiHelpers.ScaledDummy(10.0f);
115 | ImGui.Text("Color Options");
116 |
117 | ImGui.Separator();
118 | ImGuiHelpers.ScaledDummy(5.0f);
119 | DrawColorOptions();
120 | }
121 |
122 | private static void DrawFpsLimitOptions() {
123 | using var fpsInputTable = ImRaii.Table("fps_input_settings", 2);
124 | if (!fpsInputTable) return;
125 |
126 | ImGui.TableNextColumn();
127 | ImGui.AlignTextToFramePadding();
128 | ImGui.Text("Lower Limit");
129 | ImGui.SameLine();
130 | ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X * 0.75f);
131 | var idleLimit = System.Config.Limiter.IdleFramerateTarget;
132 | ImGui.InputInt("##LowerLimit", ref idleLimit);
133 | if (ImGui.IsItemDeactivatedAfterEdit()) {
134 | System.Config.Limiter.IdleFramerateTarget = Math.Clamp(idleLimit, 1, System.Config.Limiter.ActiveFramerateTarget);
135 | System.Config.Save();
136 | }
137 |
138 | ImGui.TableNextColumn();
139 | ImGui.AlignTextToFramePadding();
140 | ImGui.Text("Upper Limit");
141 | ImGui.SameLine();
142 | ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X * 0.75f);
143 | var activeLimit = System.Config.Limiter.ActiveFramerateTarget;
144 | ImGui.InputInt("##UpperLimit", ref activeLimit);
145 | if (ImGui.IsItemDeactivatedAfterEdit()) {
146 | System.Config.Limiter.ActiveFramerateTarget = Math.Clamp(activeLimit, System.Config.Limiter.IdleFramerateTarget, 1000);
147 | System.Config.Save();
148 | }
149 | }
150 |
151 | private void DrawLimiterOptions() {
152 | using var table = ImRaii.Table("limiter_options_table", 3);
153 | if (!table) return;
154 |
155 | ImGui.TableSetupColumn("Condition", ImGuiTableColumnFlags.WidthFixed, 150.0f * ImGuiHelpers.GlobalScale);
156 | ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthFixed, 75.0f * ImGuiHelpers.GlobalScale);
157 | ImGui.TableSetupColumn("When Condition Active", ImGuiTableColumnFlags.WidthStretch);
158 |
159 | ImGui.TableHeadersRow();
160 |
161 | foreach (var option in System.LimiterOptions) {
162 | DrawOption(option);
163 | }
164 | }
165 |
166 | private void DrawOption(IFrameLimiterOption option) {
167 | ImGui.TableNextColumn();
168 | ImGui.Text(option.Label);
169 |
170 | ImGui.TableNextColumn();
171 | if (option.Active) {
172 | ImGui.TextColored(KnownColor.Green.Vector(), "Active");
173 | }
174 | else {
175 | ImGui.TextColored(KnownColor.OrangeRed.Vector(), "Inactive");
176 | }
177 |
178 | ImGui.TableNextColumn();
179 | ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
180 |
181 | DrawOptionCombo(option);
182 | }
183 |
184 | private string LowerLimitString => $"Use Lower Limit ( {System.Config.Limiter.IdleFramerateTarget} fps )";
185 | private string UpperLimitString => $"Use Upper Limit ( {System.Config.Limiter.ActiveFramerateTarget} fps )";
186 |
187 | private void DrawOptionCombo(IFrameLimiterOption option) {
188 | using var combo = ImRaii.Combo($"##OptionCombo_{option.Label}", option.Enabled ? UpperLimitString : LowerLimitString);
189 | if (!combo) return;
190 |
191 | if (ImGui.Selectable(UpperLimitString, option.Enabled)) {
192 | option.Enabled = true;
193 | System.Config.Save();
194 | }
195 |
196 | if (ImGui.Selectable(LowerLimitString, !option.Enabled)) {
197 | option.Enabled = false;
198 | System.Config.Save();
199 | }
200 | }
201 |
202 | private static void DrawFeatureToggles() {
203 | using var pushIndent = ImRaii.PushIndent();
204 |
205 | if (ImGui.Checkbox("Enable", ref System.Config.General.EnableDtrBar)) {
206 | System.DtrController.UpdateEnabled();
207 | System.Config.Save();
208 | }
209 |
210 | if (ImGui.Checkbox("Show Color", ref System.Config.General.EnableDtrColor)) {
211 | System.Config.Save();
212 | }
213 | }
214 |
215 | private static void DrawColorOptions() {
216 | using var pushIndent = ImRaii.PushIndent();
217 | if (ImGui.ColorEdit4("Enabled Color", ref System.Config.General.ActiveColor)) {
218 | System.Config.Save();
219 | }
220 |
221 | if (ImGui.ColorEdit4("Disabled Color", ref System.Config.General.InactiveColor)) {
222 | System.Config.Save();
223 | }
224 | }
225 | }
--------------------------------------------------------------------------------