mirror of
https://github.com/Pathoschild/SMAPI.git
synced 2026-10-12 02:08:51 +00:00
344 lines
16 KiB
C#
344 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using StardewModdingAPI.Framework;
|
|
using StardewModdingAPI.Framework.Models;
|
|
using StardewModdingAPI.Framework.ModHelpers;
|
|
using StardewModdingAPI.Framework.ModLoading;
|
|
using StardewModdingAPI.Framework.Reflection;
|
|
using StardewModdingAPI.Internal.ConsoleWriting;
|
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
|
using StardewModdingAPI.Toolkit.Utilities;
|
|
using StardewValley.Extensions;
|
|
|
|
namespace StardewModdingAPI.Integrations.GenericModConfigMenu
|
|
{
|
|
/// <summary>Registers the SMAPI configuration with Generic Mod Config Menu.</summary>
|
|
internal class GenericModConfigMenuIntegration
|
|
{
|
|
/*********
|
|
** Fields
|
|
*********/
|
|
/// <summary>The unique mod ID for Generic Mod Config Menu.</summary>
|
|
private const string GenericModConfigMenuModId = "spacechase0.GenericModConfigMenu";
|
|
|
|
/// <summary>The minimum version of Generic Mod Config Menu supported by this integration.</summary>
|
|
private const string MinVersion = "1.9.6";
|
|
|
|
/// <summary>Encapsulates monitoring and logging.</summary>
|
|
private readonly IMonitor Monitor;
|
|
|
|
/// <summary>The core SMAPI translations.</summary>
|
|
private readonly Translator Translations;
|
|
|
|
/// <summary>Get the current SMAPI settings.</summary>
|
|
private readonly Func<SConfig> GetConfig;
|
|
|
|
/// <summary>Reload the SMAPI settings when they're changed through the config menu.</summary>
|
|
private readonly Action ReloadConfig;
|
|
|
|
/// <summary>The custom setting values which haven't been saved yet.</summary>
|
|
private readonly Dictionary<string, object> StagedSettings = [];
|
|
|
|
|
|
/*********
|
|
** Public methods
|
|
*********/
|
|
/// <summary>Construct an instance.</summary>
|
|
/// <param name="monitor"><inheritdoc cref="Monitor" path="/summary" /></param>
|
|
/// <param name="translations"><inheritdoc cref="Translations" path="/summary" /></param>
|
|
/// <param name="getConfig"><inheritdoc cref="GetConfig" path="/summary" /></param>
|
|
/// <param name="reloadConfig"><inheritdoc cref="ReloadConfig" path="/summary" /></param>
|
|
public GenericModConfigMenuIntegration(IMonitor monitor, Translator translations, Func<SConfig> getConfig, Action reloadConfig)
|
|
{
|
|
this.Monitor = monitor;
|
|
this.Translations = translations;
|
|
this.GetConfig = getConfig;
|
|
this.ReloadConfig = reloadConfig;
|
|
}
|
|
|
|
/// <summary>Register the mod config.</summary>
|
|
/// <param name="internalModRegistry">An API for fetching metadata about loaded mods.</param>
|
|
public void Register(ModRegistry internalModRegistry)
|
|
{
|
|
try
|
|
{
|
|
// init
|
|
ModMetadata smapiMod = this.CreateFakeMod();
|
|
IModRegistry modRegistry = this.CreateModRegistryHelper(internalModRegistry, this.Monitor, smapiMod);
|
|
IGenericModConfigMenuApi? api = this.GetGenericModConfigMenuApi(modRegistry, this.Monitor);
|
|
if (api is null)
|
|
return;
|
|
|
|
// register config
|
|
api.Register(smapiMod.Manifest, this.OnReset, this.Save);
|
|
var getConfig = this.GetConfig;
|
|
|
|
// add 'SMAPI features' section
|
|
api.AddSectionTitle(smapiMod.Manifest, () => this.Translations.Get("config.section.features"));
|
|
api.AddBoolOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.check-for-updates.name"),
|
|
tooltip: () => this.Translations.Get("config.check-for-updates.desc"),
|
|
getValue: () => getConfig().CheckForUpdates,
|
|
setValue: value => this.StageOption(nameof(SConfig.CheckForUpdates), value)
|
|
);
|
|
api.AddBoolOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.check-content-integrity.name"),
|
|
tooltip: () => this.Translations.Get("config.check-content-integrity.desc"),
|
|
getValue: () => getConfig().CheckContentIntegrity,
|
|
setValue: value => this.StageOption(nameof(SConfig.CheckContentIntegrity), value)
|
|
);
|
|
api.AddBoolOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.read-console-input.name"),
|
|
tooltip: () => this.Translations.Get("config.read-console-input.desc"),
|
|
getValue: () => getConfig().ListenForConsoleInput,
|
|
setValue: value => this.StageOption(nameof(SConfig.ListenForConsoleInput), value)
|
|
);
|
|
|
|
// add 'console window' section
|
|
api.AddSectionTitle(smapiMod.Manifest, () => this.Translations.Get("config.section.console-window"));
|
|
api.AddBoolOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.developer-mode.name"),
|
|
tooltip: () => this.Translations.Get("config.developer-mode.desc"),
|
|
getValue: () => getConfig().DeveloperMode,
|
|
setValue: value => this.StageOption(nameof(SConfig.DeveloperMode), value)
|
|
);
|
|
api.AddTextOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.color-scheme.name"),
|
|
tooltip: () => this.Translations.Get("config.color-scheme.desc"),
|
|
getValue: () => getConfig().ConsoleColorScheme.ToString(),
|
|
setValue: value => this.StageOption(nameof(SConfig.ConsoleColorScheme), value),
|
|
allowedValues: Enum.GetValues<MonitorColorScheme>().Select(p => p.ToString()).ToArray(),
|
|
formatAllowedValue: value => this.Translations.Get(
|
|
value is nameof(MonitorColorScheme.AutoDetect) && Constants.Platform == Platform.Windows
|
|
? $"config.color-scheme.options.{value}.on-windows"
|
|
: $"config.color-scheme.options.{value}"
|
|
));
|
|
|
|
// add 'verbose logging' section
|
|
api.AddSectionTitle(smapiMod.Manifest, () => this.Translations.Get("config.section.verbose-logs"));
|
|
api.AddParagraph(smapiMod.Manifest, () => this.Translations.Get("config.section.verbose-logs.explanation"));
|
|
api.AddTextOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.enable-for.name"),
|
|
tooltip: () => this.Translations.Get("config.enable-for.desc"),
|
|
getValue: () => getConfig().VerboseLogging.Contains("*").ToString(),
|
|
setValue: value => this.StageVerboseLoggingOption("*", value == bool.TrueString),
|
|
allowedValues: [bool.TrueString, bool.FalseString],
|
|
formatAllowedValue: value => this.Translations.Get($"config.enable-for.options.{(value == bool.TrueString ? "all" : "selected")}")
|
|
);
|
|
api.AddBoolOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.enable-for-smapi.name"),
|
|
tooltip: () => this.Translations.Get("config.enable-for-smapi.desc"),
|
|
getValue: () => getConfig().VerboseLogging.Contains("SMAPI"),
|
|
setValue: value => this.StageVerboseLoggingOption("SMAPI", value)
|
|
);
|
|
foreach (IModInfo mod in modRegistry.GetAll().OrderBy(p => p.Manifest.Name, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
if (mod.IsContentPack)
|
|
continue;
|
|
|
|
api.AddBoolOption(
|
|
mod: smapiMod.Manifest,
|
|
name: () => this.Translations.Get("config.enable-for-mod.name", new { modName = mod.Manifest.Name }),
|
|
tooltip: () => this.Translations.Get("config.enable-for-mod.desc", new { modName = mod.Manifest.Name }),
|
|
getValue: () => getConfig().VerboseLogging.Contains(mod.Manifest.UniqueID),
|
|
setValue: value => this.StageVerboseLoggingOption(mod.Manifest.UniqueID, value)
|
|
);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Monitor.Log("Couldn't register the SMAPI settings with Generic Mod Config Menu. This has no effect on SMAPI, but the in-game config menu won't be available.", LogLevel.Warn);
|
|
this.Monitor.Log(ex.ToString());
|
|
}
|
|
}
|
|
|
|
|
|
/*********
|
|
** Private methods
|
|
*********/
|
|
/****
|
|
** Save events
|
|
****/
|
|
/// <summary>Reset the SMAPI configuration to the default values.</summary>
|
|
private void OnReset()
|
|
{
|
|
try
|
|
{
|
|
foreach (string customPath in new[] { Constants.ApiUserConfigPath, Constants.ApiModGroupConfigPath })
|
|
{
|
|
if (File.Exists(customPath))
|
|
File.Delete(customPath);
|
|
}
|
|
|
|
this.ReloadConfig();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Monitor.Log($"Couldn't reset SMAPI to the default settings from the Generic Mod Config Menu UI.\n\nTechnical details:\n{ex}", LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>Save the mod configuration.</summary>
|
|
private void Save()
|
|
{
|
|
try
|
|
{
|
|
// load settings
|
|
JsonSettingsWrapper? defaultSettings = JsonSettingsWrapper.TryLoadFile(Constants.ApiConfigPath, this.Monitor);
|
|
JsonSettingsWrapper? userSettings = JsonSettingsWrapper.TryLoadFile(Constants.ApiUserConfigPath, this.Monitor);
|
|
JsonSettingsWrapper? modGroupSettings = JsonSettingsWrapper.TryLoadFile(Constants.ApiModGroupConfigPath, this.Monitor);
|
|
|
|
// skip if invalid
|
|
if (defaultSettings is null)
|
|
{
|
|
this.Monitor.Log("Couldn't save the SMAPI settings from the Generic Mod Config Menu UI. The default settings could not be loaded.", LogLevel.Error);
|
|
return;
|
|
}
|
|
|
|
// apply edited options
|
|
if (this.StagedSettings.Count > 0)
|
|
{
|
|
// create settings file if needed
|
|
if (userSettings is null && modGroupSettings is null)
|
|
userSettings = new JsonSettingsWrapper(Constants.ApiUserConfigPath);
|
|
|
|
// save simple options
|
|
foreach ((string fieldName, object newValue) in this.StagedSettings)
|
|
{
|
|
// replace existing property if possible
|
|
if (modGroupSettings?.SetUserOption(fieldName, newValue, defaultSettings, overwriteOnly: true) is true)
|
|
continue;
|
|
if (userSettings?.SetUserOption(fieldName, newValue, defaultSettings, overwriteOnly: true) is true)
|
|
continue;
|
|
|
|
// else add new property
|
|
(modGroupSettings ?? userSettings)?.SetUserOption(fieldName, newValue, defaultSettings, overwriteOnly: false);
|
|
}
|
|
}
|
|
|
|
// save to files
|
|
if (modGroupSettings?.Changed is true)
|
|
modGroupSettings.SaveOrDeleteFile();
|
|
if (userSettings?.Changed is true)
|
|
userSettings.SaveOrDeleteFile();
|
|
|
|
// reload settings
|
|
this.StagedSettings.Clear();
|
|
this.ReloadConfig();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Monitor.Log($"Couldn't save the SMAPI settings from the Generic Mod Config Menu UI.\n\nTechnical details:\n{ex}", LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
/****
|
|
** Config option staging
|
|
****/
|
|
/// <summary>Store a value set by the player before it's saved to the config files.</summary>
|
|
/// <param name="fieldName">The name of the <see cref="SConfig"/> field to change.</param>
|
|
/// <param name="value">The value to stage.</param>
|
|
private void StageOption(string fieldName, object value)
|
|
{
|
|
this.StagedSettings[fieldName] = value;
|
|
}
|
|
|
|
/// <summary>Store a custom <see cref="SConfig.VerboseLogging"/> option before it's saved to the config files.</summary>
|
|
/// <param name="id">The mod ID, 'SMAPI', or '*' (all) for which to toggle verbose logging.</param>
|
|
/// <param name="enable">Whether verbose logging is enabled.</param>
|
|
private void StageVerboseLoggingOption(string id, bool enable)
|
|
{
|
|
const string fieldName = nameof(SConfig.VerboseLogging);
|
|
|
|
if (this.StagedSettings.TryGetValue(fieldName, out object? rawValue) && rawValue is HashSet<string> set)
|
|
set.Toggle(id, enable);
|
|
else
|
|
{
|
|
HashSet<string> newSet = new HashSet<string>(this.GetConfig().VerboseLogging, StringComparer.OrdinalIgnoreCase);
|
|
newSet.Toggle(id, enable);
|
|
this.StagedSettings[fieldName] = newSet;
|
|
}
|
|
}
|
|
|
|
/****
|
|
** Initialization
|
|
****/
|
|
/// <summary>Get the public API for Generic Mod Config Menu, if it's available.</summary>
|
|
/// <param name="modRegistry">The mod registry to query.</param>
|
|
/// <param name="monitor">The monitor with which to log issues loading the API.</param>
|
|
/// <returns>Returns the API if it's available, else <c>null</c>.</returns>
|
|
private IGenericModConfigMenuApi? GetGenericModConfigMenuApi(IModRegistry modRegistry, IMonitor monitor)
|
|
{
|
|
// check mod is installed
|
|
if (modRegistry.Get(GenericModConfigMenuIntegration.GenericModConfigMenuModId) is not { } configMod)
|
|
return null;
|
|
|
|
// check min version
|
|
if (configMod.Manifest.Version.IsOlderThan(GenericModConfigMenuIntegration.MinVersion))
|
|
{
|
|
monitor.Log("Can't register the SMAPI settings with Generic Mod Config Menu, because you need version 1.9.6 or later of that mod.", LogLevel.Debug);
|
|
return null;
|
|
}
|
|
|
|
// check API
|
|
if (modRegistry.GetApi<IGenericModConfigMenuApi>(GenericModConfigMenuIntegration.GenericModConfigMenuModId) is not { } api)
|
|
{
|
|
monitor.Log("Can't register the SMAPI settings with Generic Mod Config Menu, because it unexpectedly had no API.", LogLevel.Debug);
|
|
return null;
|
|
}
|
|
|
|
return api;
|
|
}
|
|
|
|
/// <summary>Create a fake mod entry to represent SMAPI itself.</summary>
|
|
private ModMetadata CreateFakeMod()
|
|
{
|
|
IManifest manifest = new Manifest(
|
|
uniqueId: "SMAPI",
|
|
name: "SMAPI",
|
|
author: "Pathoschild",
|
|
description: string.Empty,
|
|
version: Constants.ApiVersion,
|
|
minimumApiVersion: Constants.ApiVersion,
|
|
minimumGameVersion: Constants.MinimumGameVersion,
|
|
entryDll: "StardewModdingAPI.exe",
|
|
contentPackFor: null,
|
|
dependencies: [],
|
|
updateKeys: []
|
|
);
|
|
|
|
return new ModMetadata(
|
|
displayName: "SMAPI",
|
|
directoryPath: Constants.GamePath,
|
|
rootPath: Constants.ModsPath,
|
|
manifest: manifest,
|
|
dataRecord: null,
|
|
isIgnored: false
|
|
);
|
|
}
|
|
|
|
/// <summary>Create a mod registry helper.</summary>
|
|
/// <param name="internalRegistry">The internal mod registry.</param>
|
|
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
|
/// <param name="smapiMod">The fake mod entry representing SMAPI itself.</param>
|
|
private IModRegistry CreateModRegistryHelper(ModRegistry internalRegistry, IMonitor monitor, ModMetadata smapiMod)
|
|
{
|
|
return new ModRegistryHelper(
|
|
mod: smapiMod,
|
|
registry: internalRegistry,
|
|
proxyFactory: new InterfaceProxyFactory(),
|
|
monitor: monitor
|
|
);
|
|
}
|
|
}
|
|
}
|