diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 5ee4b474a..7afe44d95 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -46,7 +46,7 @@ public class ActionGameManager : MonoBehaviour public static ShipModCatalog.Exclusion[] ModExclusions { get; private set; } = Array.Empty(); private static string CatalogPath => Path.Combine(GameDataDirectory.FullName, "Aetheria.cc"); - private static string ModsPath => Path.Combine(GameDataDirectory.FullName, "Mods"); + public static string ModsPath => Path.Combine(GameDataDirectory.FullName, "Mods"); // Disposable: composed at every boot from the shipped catalog and the mod packages, never authored. private static string DerivedCatalogPath => Path.Combine(Application.persistentDataPath, "Aetheria.modded.cc"); diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index bd019ab21..ea75d836b 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -99,7 +99,7 @@ public class MainMenu : MonoBehaviour if (ActionGameManager.ModExclusions.Length > 0 && !_exclusionsShown) { _exclusionsShown = true; - Refuse("Some mods were not loaded", string.Join("\n", ActionGameManager.ModExclusions.Select(exclusion => exclusion.ToString()))); + RefuseMods("Some mods were not loaded", ActionGameManager.ModExclusions); } var saved = cache.GetGlobal(); if (saved == null && cache.AllStoredDocuments.Any(stored => RunSave.IsRunRecord(stored.Descriptor.DocumentType))) @@ -210,7 +210,8 @@ public class MainMenu : MonoBehaviour while (!loading.IsCompleted) yield return null; if (loading.IsFaulted) { - Refuse("Mod ships failed to load", loading.Exception.GetBaseException().Message); + if (loading.Exception.GetBaseException() is ShipModShips.ImportFault fault) RefuseMods("Mod ships failed to load", fault.Faults); + else Refuse("Mod ships failed to load", loading.Exception.GetBaseException().Message); yield break; } SceneManager.LoadScene("ARPG"); @@ -225,6 +226,37 @@ public class MainMenu : MonoBehaviour Dialog.Show(onCancel: () => { }, cancelText: "OK"); } + // Refuse for faults that name mod packages: each distinct package gets a Remove button, the one RemovePackage. The catalog + // stays as this boot opened it, so the dialog says that removal takes effect at the next launch. + public void RefuseMods(string title, ShipModCatalog.Exclusion[] faults) + { + var reason = string.Join("\n", faults.Select(fault => fault.ToString())); + Debug.LogError($"{title}: {reason}"); + Dialog.Clear(); + Dialog.Title.text = title; + Dialog.AddProperty(() => reason); + foreach (var package in faults.Select(fault => fault.Package).Where(package => package != "(all mods)").Distinct()) + Dialog.AddButton($"Remove {package}", () => Remove(package)); + Dialog.Show(onCancel: () => { }, cancelText: "OK"); + } + + private void Remove(string package) + { + string destination; + try { destination = ShipModCatalog.RemovePackage(ActionGameManager.ModsPath, package); } + catch (Exception error) + { + Refuse($"Could not remove {package}", $"{error.Message} Remove it by hand from {ActionGameManager.ModsPath}."); + return; + } + var done = $"{package} moved to {destination}. Mods load at launch: restart Aetheria to play without it, or move it back into Mods to reinstall."; + Debug.Log(done); + Dialog.Clear(); + Dialog.Title.text = $"Removed {package}"; + Dialog.AddProperty(() => done); + Dialog.Show(onConfirm: Application.Quit, onCancel: () => { }, confirmText: "Quit", cancelText: "OK"); + } + private void ShowSettings() { _nextMenu.panel.Clear();