Files
HSUI/Config/ConfigurationManager.cs
T

739 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),
// 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
}
}