using OwlCore.AbstractUI.Models; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using ZuneModCore.Mods; namespace ZuneModCore { public abstract class Mod { public abstract string Id { get; } public abstract string Title { get; } public abstract string Description { get; } public abstract string Author { get; } public delegate void StatusChangedHandler(Mod sender, bool status); public event StatusChangedHandler? StatusChanged; public virtual AbstractUICollection? GetDefaultOptionsUI() => null; public virtual Task Init() => Task.CompletedTask; protected abstract Task ApplyCore(); protected abstract Task ResetCore(); protected virtual void FireStatusChanged(bool status) => StatusChanged?.Invoke(this, status); private AbstractUICollection? _OptionsUI; public AbstractUICollection? OptionsUI { get { if (_OptionsUI == null) _OptionsUI = GetDefaultOptionsUI(); return _OptionsUI; } set => _OptionsUI = value; } /// /// Gets a directory that mods can use to store mod-specific information, /// such as backups for resetting. /// public string StorageDirectory { get { string dir = Path.Combine(ModManager.CoreStorageDir, Id); // Create the directory just in case the consumer assumes the folder exists already Directory.CreateDirectory(dir); return dir; } } public abstract IReadOnlyList? DependentMods { get; } /// /// Applies this mod, without any of its dependencies. /// /// /// The error message if not successful. /// public async Task Apply() { string? error = await ApplyCore(); if (error == null) { ModManager.MarkApplied(Id); FireStatusChanged(true); } return error; } /// /// Initializes any missing depedencies. /// public Task InitDependencies() => ForAllDependenciesAsync(async mod => { bool isDepApplied = mod.CheckApplied(); if (!isDepApplied) await mod.Init(); }); /// /// Applies any missing depedencies. /// /// /// Make sure to call first. /// public Task ApplyDependencies() => ForAllDependenciesAsync(async mod => { bool isDepApplied = mod.CheckApplied(); if (!isDepApplied) await mod.Apply(); }); /// /// Applies this mod and all its dependencies. /// /// /// The error message if not successful. /// public async Task ApplyWithDependencies() { await ApplyDependencies(); return await Apply(); } /// /// Resets this mod, without resetting any dependencies. /// public async Task Reset() { string? error = await ResetCore(); if (error == null) { ModManager.MarkReset(Id); FireStatusChanged(false); } return error; } public bool CheckApplied() => ModManager.CheckStatus(Id); public bool CheckDependenciesApplied() => TrueForAllDependencies(mod => mod.CheckApplied()); private void ForAllDependencies(Action action) { // Check if there are any dependencies if (DependentMods == null) return; foreach (var dep in DependentMods) { var depMod = ModManager.AvailableMods.First(m => m.Id == dep.Id); if (depMod != null) action(depMod); } } private async Task ForAllDependenciesAsync(Func asyncAction) { // Check if there are any dependencies if (DependentMods == null) return; foreach (var dep in DependentMods) { var depMod = ModManager.AvailableMods.First(m => m.Id == dep.Id); if (depMod != null) await asyncAction(depMod); } } private bool TrueForAllDependencies(Func action) { // Check if there are any dependencies if (DependentMods == null) return true; foreach (var dep in DependentMods) { var depMod = ModManager.AvailableMods.First(m => m.Id == dep.Id); if (depMod != null) if (!action(depMod)) return false; } return true; } public override string ToString() => Title; } }