MainMenu: a surfaced broken mod gets a Remove button that moves it aside

RefuseMods names every faulted or excluded package with one Remove button per
package, all through ShipModCatalog.RemovePackage; the catalog stays as this
boot opened it and the dialog says to restart. ModsPath becomes public.
Unity-only code; not built until the Starfire smoke at the end of the cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-16 21:48:57 +02:00
commit 65a6ce8705
2 changed files with 35 additions and 3 deletions

View file

@ -46,7 +46,7 @@ public class ActionGameManager : MonoBehaviour
public static ShipModCatalog.Exclusion[] ModExclusions { get; private set; } = Array.Empty<ShipModCatalog.Exclusion>();
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");

View file

@ -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<SavedGame>();
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();