Files
HSUI/Config/ConfigurationManager.cs
2026-03-04 01:38:48 -05:00

741 lines
24 KiB
C#

using Dalamud.Interface.Internal;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using HSUI.Config.Profiles;
using HSUI.Config.Tree;
using HSUI.Config.Windows;
using HSUI.Helpers;
using HSUI.Interface;
using HSUI.Interface.EnemyList;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.Jobs;
using HSUI.Interface.Party;
using HSUI.Interface.PartyCooldowns;
using HSUI.Interface.StatusEffects;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
namespace HSUI.Config
{
public delegate void ConfigurationManagerEventHandler(ConfigurationManager configurationManager);
public delegate void StrataLevelsEventHandler(ConfigurationManager configurationManager, PluginConfigObject config);
public delegate void GlobalVisibilityEventHandler(ConfigurationManager configurationManager, VisibilityConfig config);
public class ConfigurationManager : IDisposable
{
public static ConfigurationManager Instance { get; private set; } = null!;
private BaseNode _configBaseNode;
private Dictionary<string, BaseNode> _configBaseNodeByProfile;
public BaseNode ConfigBaseNode
{
get => _configBaseNode;
set
{
_configBaseNode = value;
_mainConfigWindow.node = value;
}
}
private WindowSystem _windowSystem;
private MainConfigWindow _mainConfigWindow;
private ChangelogWindow _changelogWindow;
private GridWindow _gridWindow;
private ControllerBarKeybindsWindow _controllerBarKeybindsWindow;
public bool IsConfigWindowOpened => _mainConfigWindow.IsOpen;
public bool IsChangelogWindowOpened => _changelogWindow.IsOpen;
public bool ShowingModalWindow = false;
public GradientDirection GradientDirection
{
get
{
var config = Instance.GetConfigObject<MiscColorConfig>();
return config != null ? config.GradientDirection : GradientDirection.None;
}
}
public bool OverrideDalamudStyle
{
get
{
HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
return config != null ? config.OverrideDalamudStyle : true;
}
}
public CultureInfo ActiveCultreInfo
{
get {
HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
return config == null || config.UseRegionalNumberFormats ? CultureInfo.CurrentCulture : CultureInfo.InvariantCulture;
}
}
public string ConfigDirectory;
public string CurrentVersion => Plugin.Version;
public string? PreviousVersion { get; private set; } = null;
private bool _needsProfileUpdate = false;
private bool _lockHUD = true;
public bool LockHUD
{
get => _lockHUD;
set
{
if (_lockHUD == value)
{
return;
}
_lockHUD = value;
_mainConfigWindow.IsOpen = value;
_gridWindow.IsOpen = !value;
LockEvent?.Invoke(this);
if (_lockHUD)
{
ConfigBaseNode.NeedsSave = true;
}
}
}
public bool ShowHUD = true;
public event ConfigurationManagerEventHandler? ResetEvent;
public event ConfigurationManagerEventHandler? LockEvent;
public event ConfigurationManagerEventHandler? ConfigClosedEvent;
public event StrataLevelsEventHandler? StrataLevelsChangedEvent;
public event GlobalVisibilityEventHandler? GlobalVisibilityEvent;
public ConfigurationManager()
{
ConfigDirectory = Plugin.PluginInterface.GetPluginConfigDirectory();
_configBaseNodeByProfile = new Dictionary<string, BaseNode>();
_configBaseNode = new BaseNode();
InitializeBaseNode(_configBaseNode);
_configBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;
_mainConfigWindow = new MainConfigWindow("HSUI Settings");
_mainConfigWindow.node = _configBaseNode;
_mainConfigWindow.CloseAction = () =>
{
ConfigClosedEvent?.Invoke(this);
if (ConfigBaseNode.NeedsSave)
{
SaveConfigurations();
}
if (_needsProfileUpdate)
{
UpdateCurrentProfile();
_needsProfileUpdate = false;
}
};
string changelog = LoadChangelog();
_changelogWindow = new ChangelogWindow("HSUI Changelog v" + Plugin.Version, changelog);
_gridWindow = new GridWindow("Grid ##HSUI");
_controllerBarKeybindsWindow = new ControllerBarKeybindsWindow();
_windowSystem = new WindowSystem("HSUI_Windows");
_windowSystem.AddWindow(_mainConfigWindow);
_windowSystem.AddWindow(_changelogWindow);
_windowSystem.AddWindow(_gridWindow);
_windowSystem.AddWindow(_controllerBarKeybindsWindow);
CheckVersion();
Plugin.ClientState.Logout += OnLogout;
Plugin.ClientState.TerritoryChanged += OnTerritoryChanged;
Plugin.JobChangedEvent += OnJobChanged;
_configBaseNode.CreateNodesIfNeeded();
}
~ConfigurationManager()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
Plugin.ClientState.Logout -= OnLogout;
Plugin.ClientState.TerritoryChanged -= OnTerritoryChanged;
Plugin.JobChangedEvent -= OnJobChanged;
Instance = null!;
}
public static void Initialize() { Instance = new ConfigurationManager(); }
private void OnConfigObjectReset(BaseNode sender)
{
ResetEvent?.Invoke(this);
}
private void OnLogout(int type, int code)
{
SaveConfigurations();
ProfilesManager.Instance?.SaveCurrentProfile();
}
private void OnTerritoryChanged(ushort territoryId)
{
// Persist config on teleport/zone change so changes aren't lost during loading screens
if (ConfigBaseNode.NeedsSave)
SaveConfigurations();
}
private void OnJobChanged(uint jobId)
{
UpdateCurrentProfile();
}
private string LoadChangelog()
{
string path = Path.Combine(Plugin.AssemblyLocation, "changelog.md");
try
{
string fullChangelog = File.ReadAllText(path);
string versionChangelog = fullChangelog.Split("#", StringSplitOptions.RemoveEmptyEntries)[0];
return versionChangelog.Replace(Plugin.Version, "");
}
catch (Exception e)
{
Plugin.Logger.Error("Error loading changelog: " + e.Message);
}
return "";
}
private void CheckVersion()
{
string path = Path.Combine(ConfigDirectory, "version");
bool needsBackup = false;
try
{
bool needsWrite = false;
if (!File.Exists(path))
{
needsWrite = true;
}
else
{
PreviousVersion = File.ReadAllText(path);
if (PreviousVersion != Plugin.Version)
{
needsWrite = true;
needsBackup = true;
}
}
_changelogWindow.IsOpen = needsWrite;
_changelogWindow.AutoClose = true;
if (needsWrite)
{
File.WriteAllText(path, Plugin.Version);
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error checking version: " + e.Message);
}
try
{
if (needsBackup && PreviousVersion != null)
{
BackupFiles(PreviousVersion);
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error making backup: " + e.Message);
}
}
private void BackupFiles(string version)
{
string backupsRoot = Path.Combine(ConfigDirectory, "Backups");
if (!Directory.Exists(backupsRoot))
{
Directory.CreateDirectory(backupsRoot);
}
string backupPath = Path.Combine(backupsRoot, version);
foreach (string folderPath in Directory.GetDirectories(ConfigDirectory, "*", SearchOption.AllDirectories))
{
if (folderPath.Contains("Backups")) { continue; }
Directory.CreateDirectory(folderPath.Replace(ConfigDirectory, backupPath));
}
foreach (string filePath in Directory.GetFiles(ConfigDirectory, "*.*", SearchOption.AllDirectories))
{
File.Copy(filePath, filePath.Replace(ConfigDirectory, backupPath), true);
}
}
#region strata
public void OnStrataLevelChanged(PluginConfigObject config)
{
StrataLevelsChangedEvent?.Invoke(this, config);
}
#endregion
#region visibility
public void OnGlobalVisibilityChanged(VisibilityConfig config)
{
GlobalVisibilityEvent?.Invoke(this, config);
}
#endregion
#region windows
public void ToggleConfigWindow()
{
_mainConfigWindow.Toggle();
}
public void OpenConfigWindow()
{
_mainConfigWindow.IsOpen = true;
}
public void CloseConfigWindow()
{
_mainConfigWindow.IsOpen = false;
}
public void OpenChangelogWindow()
{
_changelogWindow.IsOpen = true;
}
public void OpenControllerBarKeybindsWindow()
{
_controllerBarKeybindsWindow.IsOpen = true;
}
public void Draw()
{
_windowSystem.Draw();
}
public void AddExtraSectionNode(SectionNode node)
{
ConfigBaseNode.AddExtraSectionNode(node);
}
#endregion
#region config getters and setters
public PluginConfigObject GetConfigObjectForType(Type type)
{
MethodInfo? genericMethod = GetType().GetMethod("GetConfigObject");
MethodInfo? method = genericMethod?.MakeGenericMethod(type);
return (PluginConfigObject)method?.Invoke(this, null)!;
}
public T GetConfigObject<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigObject<T>()!;
public static PluginConfigObject GetDefaultConfigObjectForType(Type type)
{
MethodInfo? method = type.GetMethod("DefaultConfig", BindingFlags.Public | BindingFlags.Static);
return (PluginConfigObject)method?.Invoke(null, null)!;
}
public ConfigPageNode GetConfigPageNode<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigPageNode<T>()!;
public void SetConfigObject(PluginConfigObject configObject) => ConfigBaseNode.SetConfigObject(configObject);
public List<T> GetObjects<T>() => ConfigBaseNode.GetObjects<T>();
#endregion
#region load / save / profiles
public bool IsFreshInstall()
{
return Directory.GetDirectories(ConfigDirectory).Length == 0;
}
public void LoadOrInitializeFiles()
{
try
{
if (!IsFreshInstall())
{
LoadConfigurations();
// gotta save after initial load store possible version update changes right away
SaveConfigurations(true);
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error initializing configurations: " + e.Message);
if (e.StackTrace != null)
{
Plugin.Logger.Error(e.StackTrace);
}
}
}
public void ForceNeedsSave()
{
ConfigBaseNode.NeedsSave = true;
}
public void LoadConfigurations()
{
PerformV2Migration();
ConfigBaseNode.Load(ConfigDirectory);
}
public void SaveConfigurations(bool forced = false)
{
if (!forced && !ConfigBaseNode.NeedsSave)
{
return;
}
ConfigBaseNode.Save(ConfigDirectory);
ProfilesManager.Instance?.SaveCurrentProfile();
ConfigBaseNode.NeedsSave = false;
}
public void PerformV2Migration()
{
// create necessary folders
string[] newFolders = new string[] { "Other Elements", "Customization" };
foreach (string folder in newFolders)
{
string path = Path.Combine(ConfigDirectory, folder);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
// move files
Dictionary<string, string> files = new Dictionary<string, string>()
{
["Misc\\Experience Bar.json"] = "Other Elements\\Experience Bar.json",
["Misc\\GCD Indicator.json"] = "Other Elements\\GCD Indicator.json",
["Misc\\Limit Break.json"] = "Other Elements\\Limit Break.json",
["Misc\\MP Ticker.json"] = "Other Elements\\MP Ticker.json",
["Misc\\Pull Timer.json"] = "Other Elements\\Pull Timer.json",
["Misc\\Fonts.json"] = "Customization\\Fonts.json"
};
foreach (string key in files.Keys)
{
string v1Path = Path.Combine(ConfigDirectory, key);
string v2Path = Path.Combine(ConfigDirectory, files[key]);
try
{
if (File.Exists(v1Path) && !File.Exists(v2Path))
{
File.Move(v1Path, v2Path);
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error migrating file \"" + v1Path + "\" to v2 config structure: " + e.Message);
}
}
}
public void UpdateCurrentProfile()
{
// dont update the profile on job change when the config window is opened
if (_mainConfigWindow.IsOpen)
{
_needsProfileUpdate = true;
return;
}
ProfilesManager.Instance?.UpdateCurrentProfile();
}
public string? ExportCurrentConfigs()
{
return ConfigBaseNode.GetBase64String();
}
public void OnProfileDeleted(string profileName)
{
try
{
_configBaseNodeByProfile.Remove(profileName);
}
catch { }
}
public bool ImportProfile(string oldProfileName, string profileName, string rawString, bool forceLoad = false)
{
// cache old profile
_configBaseNodeByProfile[oldProfileName] = ConfigBaseNode;
// load profile from cache or from rawString
BaseNode? loadedNode = null;
if (forceLoad || !_configBaseNodeByProfile.TryGetValue(profileName, out loadedNode))
{
ImportProfileNonCached(rawString, out loadedNode);
}
if (loadedNode == null) { return false; }
if (IsConfigWindowOpened || string.IsNullOrEmpty(loadedNode.SelectedOptionName))
{
loadedNode.SelectedOptionName = ConfigBaseNode.SelectedOptionName;
loadedNode.RefreshSelectedNode();
}
ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
ConfigBaseNode = loadedNode;
ConfigBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;
PerformV2Migration();
ResetEvent?.Invoke(this);
return true;
}
private bool ImportProfileNonCached(string rawString, out BaseNode? node)
{
List<string> importStrings = new List<string>(rawString.Trim().Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries));
ImportData[] imports = importStrings.Select(str => new ImportData(str)).ToArray();
node = new BaseNode();
InitializeBaseNode(node);
Dictionary<Type, PluginConfigObject> oldConfigObjects = new Dictionary<Type, PluginConfigObject>();
foreach (ImportData importData in imports)
{
PluginConfigObject? config = importData.GetObject();
if (config == null)
{
return false;
}
if (!node.SetConfigObject(config))
{
oldConfigObjects.Add(config.GetType(), config);
}
}
if (ProfilesManager.Instance != null)
{
node.AddExtraSectionNode(ProfilesManager.Instance.ProfilesNode);
}
return true;
}
#endregion
#region initialization
private static void InitializeBaseNode(BaseNode node)
{
// creates node tree in the right order...
foreach (Type type in ConfigObjectTypes)
{
var genericMethod = node.GetType().GetMethod("GetConfigPageNode");
var method = genericMethod?.MakeGenericMethod(type);
method?.Invoke(node, null);
}
}
private static Type[] ConfigObjectTypes = new Type[]
{
// Unit Frames
typeof(PlayerUnitFrameConfig),
typeof(TargetUnitFrameConfig),
typeof(TargetOfTargetUnitFrameConfig),
typeof(FocusTargetUnitFrameConfig),
// Mana Bars
typeof(PlayerPrimaryResourceConfig),
typeof(TargetPrimaryResourceConfig),
typeof(TargetOfTargetPrimaryResourceConfig),
typeof(FocusTargetPrimaryResourceConfig),
// Castbars
typeof(PlayerCastbarConfig),
typeof(TargetCastbarConfig),
typeof(TargetOfTargetCastbarConfig),
typeof(FocusTargetCastbarConfig),
// Buffs and Debuffs
typeof(PlayerBuffsListConfig),
typeof(PlayerDebuffsListConfig),
typeof(TargetBuffsListConfig),
typeof(TargetDebuffsListConfig),
typeof(FocusTargetBuffsListConfig),
typeof(FocusTargetDebuffsListConfig),
typeof(CustomEffectsListConfig),
// Nameplates
typeof(NameplatesGeneralConfig),
typeof(PlayerNameplateConfig),
typeof(EnemyNameplateConfig),
typeof(PartyMembersNameplateConfig),
typeof(AllianceMembersNameplateConfig),
typeof(FriendPlayerNameplateConfig),
typeof(OtherPlayerNameplateConfig),
typeof(PetNameplateConfig),
typeof(NPCNameplateConfig),
typeof(MinionNPCNameplateConfig),
typeof(ObjectsNameplateConfig),
// Party Frames
typeof(PartyFramesConfig),
typeof(AllianceFramesHealthBarsConfig),
typeof(AllianceFramesManaBarConfig),
typeof(AllianceFramesCastbarConfig),
typeof(AllianceFramesIconsConfig),
typeof(AllianceFramesBuffsConfig),
typeof(AllianceFramesDebuffsConfig),
typeof(AllianceFramesTrackersConfig),
typeof(AllianceFramesCooldownListConfig),
typeof(AllianceFrames1Config),
typeof(AllianceFrames2Config),
typeof(PartyFramesHealthBarsConfig),
typeof(PartyFramesManaBarConfig),
typeof(PartyFramesCastbarConfig),
typeof(PartyFramesIconsConfig),
typeof(PartyFramesBuffsConfig),
typeof(PartyFramesDebuffsConfig),
typeof(PartyFramesTrackersConfig),
typeof(PartyFramesCooldownListConfig),
// Party Cooldowns
typeof(PartyCooldownsConfig),
typeof(PartyCooldownsBarConfig),
typeof(PartyCooldownsDataConfig),
// Enemy List
typeof(EnemyListConfig),
typeof(EnemyListHealthBarConfig),
typeof(EnemyListEnmityIconConfig),
typeof(EnemyListSignIconConfig),
typeof(EnemyListCastbarConfig),
typeof(EnemyListBuffsConfig),
typeof(EnemyListDebuffsConfig),
// Job Specific Bars
typeof(PaladinConfig),
typeof(WarriorConfig),
typeof(DarkKnightConfig),
typeof(GunbreakerConfig),
typeof(WhiteMageConfig),
typeof(ScholarConfig),
typeof(AstrologianConfig),
typeof(SageConfig),
typeof(MonkConfig),
typeof(DragoonConfig),
typeof(NinjaConfig),
typeof(SamuraiConfig),
typeof(ReaperConfig),
typeof(ViperConfig),
typeof(BardConfig),
typeof(MachinistConfig),
typeof(DancerConfig),
typeof(BlackMageConfig),
typeof(SummonerConfig),
typeof(RedMageConfig),
typeof(BlueMageConfig),
typeof(PictomancerConfig),
// Other Elements
typeof(ExperienceBarConfig),
typeof(GCDIndicatorConfig),
typeof(MouseGCDIndicatorConfig),
typeof(HotbarsConfig),
typeof(Hotbar1BarConfig),
typeof(Hotbar2BarConfig),
typeof(Hotbar3BarConfig),
typeof(Hotbar4BarConfig),
typeof(Hotbar5BarConfig),
typeof(Hotbar6BarConfig),
typeof(Hotbar7BarConfig),
typeof(Hotbar8BarConfig),
typeof(Hotbar9BarConfig),
typeof(Hotbar10BarConfig),
typeof(ControllerHotbarsConfig),
typeof(ControllerBar1Config),
typeof(ControllerBar2Config),
typeof(ControllerBar3Config),
typeof(ControllerBar4Config),
typeof(ControllerBar5Config),
typeof(ControllerBar6Config),
typeof(ControllerBar7Config),
typeof(ControllerBar8Config),
typeof(ControllerBarKeybindsConfig),
typeof(PullTimerConfig),
typeof(LimitBreakConfig),
typeof(MPTickerConfig),
typeof(DutyListScenarioConfig),
typeof(MainMenuConfig),
// Colors
typeof(TanksColorConfig),
typeof(HealersColorConfig),
typeof(MeleeColorConfig),
typeof(RangedColorConfig),
typeof(CastersColorConfig),
typeof(RolesColorConfig),
typeof(MiscColorConfig),
// Customization
typeof(FontsConfig),
typeof(BarTexturesConfig),
// Visibility
typeof(GlobalVisibilityConfig),
// Misc
typeof(HUDOptionsConfig),
typeof(WindowClippingConfig),
typeof(TooltipsConfig),
typeof(GridConfig),
// Import
typeof(ImportConfig)
};
#endregion
}
}