diff --git a/BSIPA-ModList/BSIPA-ModList.csproj b/BSIPA-ModList/BSIPA-ModList.csproj index cd96fcd9..9e4eb4a8 100644 --- a/BSIPA-ModList/BSIPA-ModList.csproj +++ b/BSIPA-ModList/BSIPA-ModList.csproj @@ -88,6 +88,7 @@ + diff --git a/BSIPA-ModList/Plugin.cs b/BSIPA-ModList/Plugin.cs index ac141662..b60c24a2 100644 --- a/BSIPA-ModList/Plugin.cs +++ b/BSIPA-ModList/Plugin.cs @@ -97,6 +97,9 @@ namespace BSIPA_ModList if (ButtonUI.Instance == null) new GameObject("BSIPA Mod List Object").AddComponent().Init(); + + if (WarningUI.Instance == null) + new GameObject("BSIPA Mod List Warning UI").AddComponent().Init(); } } diff --git a/BSIPA-ModList/UI/WarningUI.cs b/BSIPA-ModList/UI/WarningUI.cs new file mode 100644 index 00000000..ecbfa2d0 --- /dev/null +++ b/BSIPA-ModList/UI/WarningUI.cs @@ -0,0 +1,149 @@ +using IPA.Loader; +using IPA.Utilities; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace BSIPA_ModList.UI +{ + internal struct WarningEntry + { + public string ModName; + public string[] MissingDependencies; + public string[] DisabledDependencies; + + public WarningEntry(string modName, string[] missingDependencies, string[] disabledDependencies) + { + ModName = modName; + MissingDependencies = missingDependencies; + DisabledDependencies = disabledDependencies; + } + } + + internal class WarningUI : MonoBehaviour + { + internal static WarningUI Instance; + internal static bool firstShow = true; + + public void Awake() + { + DontDestroyOnLoad(gameObject); + SceneManager.activeSceneChanged += this.SceneManager_activeSceneChanged; + } + + private void SceneManager_activeSceneChanged(Scene from, Scene to) + { + if (to.name == "EmptyTransition") + { + if (Instance != null) + { + Instance.StopAllCoroutines(); + Destroy(Instance.gameObject); + _mainFlow = null; + } + Instance = null; + } + } + + public void Init() + { + Instance = this; + + Logger.log.Debug("Warning UI Awake"); + if (firstShow) + { + firstShow = false; + StartCoroutine(LookForUnmetDependencies()); + } + } + + private static MainFlowCoordinator _mainFlow; + private static SimpleDialogPromptViewController _warningDialog; + private static Queue _warningsQueue = new Queue(); + + private static IEnumerator LookForUnmetDependencies() + { + Logger.log.Debug("Waiting for MainFlowCoordinator to appear..."); + + yield return new WaitWhile(() => FindObjectOfType() == null); + + Logger.log.Debug("Looking for unmet dependencies..."); + + lock (Instance) + { + if (_mainFlow == null) + { + _mainFlow = FindObjectOfType(); + _warningDialog = _mainFlow.GetPrivateField("_simpleDialogPromptViewController"); + } + + _warningsQueue.Clear(); + Dictionary loadedPlugins = PluginManager.AllPlugins.Select(x => x.Metadata).Concat(PluginManager.DisabledPlugins).Concat(PluginLoader.ignoredPlugins).ToDictionary(x => x.Id, y => y.Version); + + foreach (var meta in PluginManager.AllPlugins.Select(x => x.Metadata).Concat(PluginManager.DisabledPlugins).Concat(PluginLoader.ignoredPlugins)) + { + List disabledDependencies = new List(); + List missingDependencies = new List(); + foreach (var dep in meta.Manifest.Dependencies) + { +#if DEBUG + Logger.log.Debug($"Looking for dependency {dep.Key} with version range {dep.Value.Intersect(new SemVer.Range("*.*.*"))}"); +#endif + if (loadedPlugins.ContainsKey(dep.Key) && dep.Value.IsSatisfied(loadedPlugins[dep.Key])) + continue; + + if (loadedPlugins.ContainsKey(dep.Key) && dep.Value.IsSatisfied(loadedPlugins[dep.Key])) + { + Logger.log.Debug($"Dependency {dep.Key} was found, but disabled."); + disabledDependencies.Add($"{dep.Key}@{dep.Value.ToString()}"); + } + else + { + Logger.log.Debug($"{meta.Name} is missing dependency {dep.Key}@{dep.Value}"); + missingDependencies.Add($"{dep.Key}@{dep.Value.ToString()}"); + } + } + + if(disabledDependencies.Count > 0 || missingDependencies.Count > 0) + { + _warningsQueue.Enqueue(new WarningEntry(meta.Name, missingDependencies.ToArray(), disabledDependencies.ToArray())); + } + } + + if (_warningsQueue.Count > 0) + { + yield return new WaitWhile(() => !_mainFlow.isActivated); + + ShowWarningDialog(); + } + + yield break; + } + } + + private static void ShowWarningDialog() + { + WarningEntry warning = _warningsQueue.Dequeue(); + _warningDialog.Init("Unmet Dependencies", $"Mod {warning.ModName} has unmet dependencies!" + + (warning.MissingDependencies.Length > 0 ? $"\nMissing:\n{string.Join("\n", warning.MissingDependencies)}" : "") + + (warning.DisabledDependencies.Length > 0 ? $"\nDisabled:\n{string.Join("\n", warning.DisabledDependencies)}" : "") + , "Okay", WarningDialogDidFinish); + _mainFlow.InvokePrivateMethod("PresentViewController", _warningDialog, null, true); + } + + private static void WarningDialogDidFinish(int button) + { + _mainFlow.InvokePrivateMethod("DismissViewController", _warningDialog, null, (_warningsQueue.Count > 0)); + + if (_warningsQueue.Count > 0) + { + ShowWarningDialog(); + } + } + } +}