From 80da308d1754203b6eb0a270bd233ad11b72600a Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 19 Nov 2025 22:06:15 -0600 Subject: [PATCH] Add ModConfig integration for card set info display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrate with ModOptions workshop mod to display loaded card sets information in the game's Settings menu. Uses read-only dropdowns (single-option) to show data without allowing user modification. Features: - ModConfigApi.cs wrapper for safe ModConfig interaction - Auto-detect ModConfig availability via OnModActivated event - Display total cards, total packs, and per-set card counts - Graceful fallback when ModConfig not installed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/ModBehaviour.cs | 114 +++++++++++++++ src/ModConfigApi.cs | 331 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+) create mode 100644 src/ModConfigApi.cs diff --git a/src/ModBehaviour.cs b/src/ModBehaviour.cs index 304d144..f333793 100644 --- a/src/ModBehaviour.cs +++ b/src/ModBehaviour.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using UnityEngine; using ItemStatsSystem; using SodaCraft.Localizations; +using Duckov.Modding; using Duckov.Utilities; namespace TradingCardMod @@ -17,6 +18,11 @@ namespace TradingCardMod private static ModBehaviour? _instance; public static ModBehaviour Instance => _instance!; + /// + /// Mod name for ModConfig integration. + /// + public const string MOD_NAME = "TradingCardMod"; + /// /// Gets the list of registered card items (for loot injection). /// @@ -128,6 +134,114 @@ namespace TradingCardMod } } + /// + /// Called when the mod is enabled. Set up ModConfig integration. + /// + void OnEnable() + { + ModManager.OnModActivated += OnModActivated; + + // Check if ModConfig is already loaded + if (ModConfigAPI.IsAvailable()) + { + Debug.Log("[TradingCardMod] ModConfig already available"); + SetupModConfig(); + } + } + + /// + /// Called when another mod is activated. + /// + private void OnModActivated(ModInfo info, Duckov.Modding.ModBehaviour behaviour) + { + if (info.name == ModConfigAPI.ModConfigName) + { + Debug.Log("[TradingCardMod] ModConfig activated"); + SetupModConfig(); + } + } + + /// + /// Called when the mod is disabled. Clean up ModConfig subscriptions. + /// + void OnDisable() + { + ModManager.OnModActivated -= OnModActivated; + } + + /// + /// Set up ModConfig to display loaded card sets information. + /// Uses dropdowns with single options to create read-only displays. + /// + private void SetupModConfig() + { + if (!ModConfigAPI.IsAvailable()) + { + return; + } + + // Build info string showing loaded card sets + var setInfo = new List(); + foreach (var setEntry in _cardsBySet) + { + setInfo.Add($"{setEntry.Key}: {setEntry.Value.Count} cards"); + } + + string loadedSetsInfo = setInfo.Count > 0 + ? string.Join(", ", setInfo) + : "No card sets loaded"; + + // Use dropdowns with single options to create read-only displays + // Total cards display + var cardsOption = new System.Collections.Generic.SortedDictionary + { + { $"{_loadedCards.Count} cards", _loadedCards.Count } + }; + ModConfigAPI.SafeAddDropdownList( + MOD_NAME, + "TotalCards", + "Total Cards Loaded", + cardsOption, + typeof(int), + _loadedCards.Count + ); + + // Total packs display + var packsOption = new System.Collections.Generic.SortedDictionary + { + { $"{_registeredPacks.Count} packs", _registeredPacks.Count } + }; + ModConfigAPI.SafeAddDropdownList( + MOD_NAME, + "TotalPacks", + "Total Packs Loaded", + packsOption, + typeof(int), + _registeredPacks.Count + ); + + // Card sets info - one entry per set for clarity + int setIndex = 0; + foreach (var setEntry in _cardsBySet) + { + var setOption = new System.Collections.Generic.SortedDictionary + { + { $"{setEntry.Value.Count} cards", setEntry.Value.Count } + }; + ModConfigAPI.SafeAddDropdownList( + MOD_NAME, + $"Set_{setIndex}", + $"Set: {setEntry.Key}", + setOption, + typeof(int), + setEntry.Value.Count + ); + setIndex++; + } + + Debug.Log("[TradingCardMod] ModConfig setup completed"); + } + /// /// Scans the CardSets directory and loads all card definitions. /// diff --git a/src/ModConfigApi.cs b/src/ModConfigApi.cs new file mode 100644 index 0000000..8e3afa2 --- /dev/null +++ b/src/ModConfigApi.cs @@ -0,0 +1,331 @@ +using System; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace TradingCardMod +{ + /// + /// ModConfig Safe API Wrapper Class - Provides non-throwing static interfaces. + /// Adapted from ModConfigExample by FrozenFish259. + /// + public static class ModConfigAPI + { + public static string ModConfigName = "ModConfig"; + + // Ensure this matches the number of ModConfig.ModBehaviour.VERSION + private const int ModConfigVersion = 1; + + private static string TAG = $"[TradingCardMod] ModConfig_v{ModConfigVersion}"; + + private static Type modBehaviourType; + private static Type optionsManagerType; + public static bool isInitialized = false; + private static bool versionChecked = false; + private static bool isVersionCompatible = false; + + /// + /// Check version compatibility. + /// + private static bool CheckVersionCompatibility() + { + if (versionChecked) + return isVersionCompatible; + + try + { + FieldInfo versionField = modBehaviourType.GetField("VERSION", BindingFlags.Public | BindingFlags.Static); + if (versionField != null && versionField.FieldType == typeof(int)) + { + int modConfigVersion = (int)versionField.GetValue(null); + isVersionCompatible = (modConfigVersion == ModConfigVersion); + + if (!isVersionCompatible) + { + Debug.LogError($"{TAG} Version mismatch! API version: {ModConfigVersion}, ModConfig version: {modConfigVersion}"); + return false; + } + + Debug.Log($"{TAG} Version check passed: {ModConfigVersion}"); + versionChecked = true; + return true; + } + else + { + Debug.LogWarning($"{TAG} Version field not found, skipping version check"); + isVersionCompatible = true; + versionChecked = true; + return true; + } + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Version check failed: {ex.Message}"); + isVersionCompatible = false; + versionChecked = true; + return false; + } + } + + /// + /// Initialize ModConfigAPI, check if necessary functions exist. + /// + public static bool Initialize() + { + try + { + if (isInitialized) + return true; + + modBehaviourType = FindTypeInAssemblies("ModConfig.ModBehaviour"); + if (modBehaviourType == null) + { + Debug.LogWarning($"{TAG} ModConfig.ModBehaviour type not found, ModConfig may not be loaded"); + return false; + } + + optionsManagerType = FindTypeInAssemblies("ModConfig.OptionsManager_Mod"); + if (optionsManagerType == null) + { + Debug.LogWarning($"{TAG} ModConfig.OptionsManager_Mod type not found"); + return false; + } + + if (!CheckVersionCompatibility()) + { + Debug.LogWarning($"{TAG} ModConfig version mismatch!"); + return false; + } + + string[] requiredMethods = { + "AddDropdownList", + "AddInputWithSlider", + "AddBoolDropdownList", + "AddOnOptionsChangedDelegate", + "RemoveOnOptionsChangedDelegate", + }; + + foreach (string methodName in requiredMethods) + { + MethodInfo method = modBehaviourType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); + if (method == null) + { + Debug.LogError($"{TAG} Required method {methodName} not found"); + return false; + } + } + + isInitialized = true; + Debug.Log($"{TAG} ModConfigAPI initialized successfully"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Initialization failed: {ex.Message}"); + return false; + } + } + + /// + /// Find type in all loaded assemblies. + /// + private static Type FindTypeInAssemblies(string typeName) + { + try + { + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (Assembly assembly in assemblies) + { + try + { + Type type = assembly.GetType(typeName); + if (type != null) + { + Debug.Log($"{TAG} Found type {typeName} in assembly {assembly.FullName}"); + return type; + } + } + catch + { + continue; + } + } + + return null; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Assembly scan failed: {ex.Message}"); + return null; + } + } + + /// + /// Safely add options changed event delegate. + /// + public static bool SafeAddOnOptionsChangedDelegate(Action action) + { + if (!Initialize()) + return false; + + if (action == null) + return false; + + try + { + MethodInfo method = modBehaviourType.GetMethod("AddOnOptionsChangedDelegate", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { action }); + return true; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Failed to add options changed delegate: {ex.Message}"); + return false; + } + } + + /// + /// Safely remove options changed event delegate. + /// + public static bool SafeRemoveOnOptionsChangedDelegate(Action action) + { + if (!Initialize()) + return false; + + if (action == null) + return false; + + try + { + MethodInfo method = modBehaviourType.GetMethod("RemoveOnOptionsChangedDelegate", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { action }); + return true; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Failed to remove options changed delegate: {ex.Message}"); + return false; + } + } + + /// + /// Safely add input with slider configuration item. + /// + public static bool SafeAddInputWithSlider(string modName, string key, string description, Type valueType, object defaultValue, Vector2? sliderRange = null) + { + key = $"{modName}_{key}"; + + if (!Initialize()) + return false; + + try + { + MethodInfo method = modBehaviourType.GetMethod("AddInputWithSlider", BindingFlags.Public | BindingFlags.Static); + + object[] parameters = sliderRange.HasValue ? + new object[] { modName, key, description, valueType, defaultValue, sliderRange.Value } : + new object[] { modName, key, description, valueType, defaultValue, null }; + + method.Invoke(null, parameters); + + Debug.Log($"{TAG} Added input with slider: {modName}.{key}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Failed to add input with slider {modName}.{key}: {ex.Message}"); + return false; + } + } + + /// + /// Safely add boolean dropdown list configuration item. + /// + public static bool SafeAddBoolDropdownList(string modName, string key, string description, bool defaultValue) + { + key = $"{modName}_{key}"; + + if (!Initialize()) + return false; + + try + { + MethodInfo method = modBehaviourType.GetMethod("AddBoolDropdownList", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { modName, key, description, defaultValue }); + + Debug.Log($"{TAG} Added bool dropdown: {modName}.{key}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Failed to add bool dropdown {modName}.{key}: {ex.Message}"); + return false; + } + } + + /// + /// Safely add dropdown list configuration item. + /// + public static bool SafeAddDropdownList(string modName, string key, string description, System.Collections.Generic.SortedDictionary options, Type valueType, object defaultValue) + { + key = $"{modName}_{key}"; + + if (!Initialize()) + return false; + + try + { + MethodInfo method = modBehaviourType.GetMethod("AddDropdownList", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { modName, key, description, options, valueType, defaultValue }); + + Debug.Log($"{TAG} Added dropdown list: {modName}.{key}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Failed to add dropdown list {modName}.{key}: {ex.Message}"); + return false; + } + } + + /// + /// Safely load configuration value. + /// + public static T SafeLoad(string mod_name, string key, T defaultValue = default(T)) + { + key = $"{mod_name}_{key}"; + + if (!Initialize()) + return defaultValue; + + if (string.IsNullOrEmpty(key)) + return defaultValue; + + try + { + MethodInfo loadMethod = optionsManagerType.GetMethod("Load", BindingFlags.Public | BindingFlags.Static); + if (loadMethod == null) + return defaultValue; + + MethodInfo genericLoadMethod = loadMethod.MakeGenericMethod(typeof(T)); + object result = genericLoadMethod.Invoke(null, new object[] { key, defaultValue }); + + return (T)result; + } + catch (Exception ex) + { + Debug.LogError($"{TAG} Failed to load config {key}: {ex.Message}"); + return defaultValue; + } + } + + /// + /// Check if ModConfig is available. + /// + public static bool IsAvailable() + { + return Initialize(); + } + } +}