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[] IgnoredDependencies; public string[] DisabledDependencies; public WarningEntry(string modName, string[] missingDependencies, string[] ignoredDependencies, string[] disabledDependencies) { ModName = modName; MissingDependencies = missingDependencies; IgnoredDependencies = ignoredDependencies; DisabledDependencies = disabledDependencies; } } internal class WarningUI : MonoBehaviour { // TODO: rework this to just use disable/ignore reason 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(); var enabledPlugins = PluginManager.AllPlugins.Select(p => p.Metadata).Where(x => x.Id != null).ToDictionary(x => x.Id, y => y.Version); var ignoredPlugins = PluginLoader.ignoredPlugins.Where(x => x.Id != null).ToDictionary(x => x.Id, y => y.Version); var disabledPlugins = PluginManager.DisabledPlugins.Where(x => x.Id != null).ToDictionary(x => x.Id, y => y.Version); // iterate only disabled and ignored, as thats where missing deps can end up foreach (var meta in PluginManager.DisabledPlugins.Concat(PluginLoader.ignoredPlugins)) { List disabledDependencies = new List(); List ignoredDependencies = 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 (disabledPlugins.TryGetValue(dep.Key, out var version) && dep.Value.IsSatisfied(version)) { Logger.log.Debug($"Dependency {dep.Key} was found, but disabled."); disabledDependencies.Add($"{dep.Key} {dep.Value.ToString()}"); } else if (ignoredPlugins.TryGetValue(dep.Key, out version) && dep.Value.IsSatisfied(version)) { Logger.log.Debug($"Dependency {dep.Key} was found, but was ignored, likely due to a missing dependency."); ignoredDependencies.Add($"{dep.Key} {dep.Value.ToString()}"); } else if (enabledPlugins.TryGetValue(dep.Key, out version) && dep.Value.IsSatisfied(version)) { // do nothing, this was probably user disabled } else { Logger.log.Debug($"{meta.Name} is missing dependency {dep.Key} {dep.Value}"); missingDependencies.Add($"{dep.Key} {dep.Value.ToString()}"); } } if(disabledDependencies.Count > 0 || ignoredDependencies.Count > 0 || missingDependencies.Count > 0) _warningsQueue.Enqueue(new WarningEntry(meta.Name, missingDependencies.ToArray(), ignoredDependencies.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.IgnoredDependencies.Length > 0 ? $"\nIgnored:\n{string.Join("\n", warning.IgnoredDependencies)}" : "") + (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(); } } } }