260 lines
10 KiB
C#
260 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Text.RegularExpressions;
|
||
using System.Threading.Tasks;
|
||
using SLSFramework.General;
|
||
using UMod;
|
||
using UMod.Scripting;
|
||
using UnityEngine;
|
||
using Object = UnityEngine.Object;
|
||
|
||
namespace SLSFramework.UModAssistance
|
||
{
|
||
public static partial class ModManager
|
||
{
|
||
public static ModHost LoadMod(IModInfo modInfo)
|
||
{
|
||
string modName = modInfo.NameInfo.ModName;
|
||
ModHost host = Mod.Load(Mod.DefaultDirectory.GetModPath(modName));
|
||
LoadedMods.Add(modName, host);
|
||
Debug.Log($"Mod '{modName}' loaded successfully.");
|
||
return host;
|
||
}
|
||
|
||
public static async Task<ModHost> LoadAsync(IModInfo modInfo)
|
||
{
|
||
string modName = modInfo.NameInfo.ModName;
|
||
var host = Mod.LoadAsync(Mod.DefaultDirectory.GetModPath(modName));
|
||
while (!host.IsDone)
|
||
{
|
||
await Task.Yield();
|
||
}
|
||
|
||
if (!host.IsSuccessful)
|
||
{
|
||
Debug.LogError($"[ModLoader] Mod load operation for '{modName}' failed (hostTask.IsSuccessful == false).");
|
||
throw new Exception($"Failed to load mod '{modName}' asynchronously (Operation Failed)");
|
||
}
|
||
|
||
ModHost modHost = host.Result;
|
||
|
||
if (modHost == null || !modHost.IsModLoaded)
|
||
{
|
||
if (modHost != null)
|
||
{
|
||
var errorMessage = modHost.LoadResult.Message;
|
||
Debug.LogError($"[ModLoader] Mod load operation for '{modName}' failed: {errorMessage}");
|
||
}
|
||
|
||
throw new Exception($"Failed to load mod '{modName}' asynchronously (ModHost is null or not loaded)");
|
||
}
|
||
|
||
LoadedMods.Add(modName, host.Result);
|
||
Debug.Log($"Mod '{modName}' async loaded successfully.");
|
||
return modHost;
|
||
}
|
||
}
|
||
|
||
public static partial class ModManager
|
||
{
|
||
public static readonly SerializableDictionary<string, ModHost> LoadedMods = new SerializableDictionary<string, ModHost>();
|
||
public static readonly Dictionary<Type, Dictionary<string, ScriptableObject>> Database = new Dictionary<Type, Dictionary<string, ScriptableObject>>();
|
||
|
||
/// <summary>
|
||
/// Get the standardized class name for a mod class, combining its class name and mod name.
|
||
/// Format: "ModName_ClassName"
|
||
/// </summary>
|
||
public static string GetModClassName(Type type)
|
||
{
|
||
string modName = type.Namespace!.Replace("Continentis.Mods.", "").Split('.')[0];
|
||
string className = type.Name;
|
||
return $"{modName}_{className}";
|
||
}
|
||
|
||
public static bool IsValidAssetName(string assetName) => Regex.IsMatch(assetName, @"^\w+_\w+_.+$");
|
||
|
||
/// <summary>
|
||
/// Get asset by its name, automatically determining which mod it belongs to.
|
||
/// </summary>
|
||
/// <param name="assetName">Name of the asset <b>MUST</b> in the format "Type_ModName_AssetName"</param>
|
||
public static T GetAsset<T>(string assetName) where T : Object
|
||
{
|
||
//命名符合“Type_ModName_AssetName”格式规范
|
||
if (IsValidAssetName(assetName))
|
||
{
|
||
string assumeModName = assetName.Split('_')[1];
|
||
|
||
if (LoadedMods.TryGetValue(assumeModName, out ModHost host))
|
||
{
|
||
T asset = host.Assets.Load<T>(assetName);
|
||
if (asset != null)
|
||
{
|
||
return asset;
|
||
}
|
||
}
|
||
|
||
Debug.LogWarning($"Mod '{assumeModName}' is not loaded, or cannot get asset '{assetName}'.");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"Please check the name format (Type_ModName_AssetName) of this asset, '{assetName}'.");
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get asset from specified mod.
|
||
/// </summary>
|
||
/// <param name="modName">Name of the mod</param>
|
||
/// <param name="assetName">Name of the asset, recommend name format "Type_ModName_AssetName"</param>
|
||
public static T GetAsset<T>(string modName, string assetName) where T : Object
|
||
{
|
||
if (!IsValidAssetName(assetName))
|
||
{
|
||
Debug.LogWarning($"Asset name '{assetName}' does not follow the 'Type_ModName_AssetName' format.");
|
||
}
|
||
|
||
if (LoadedMods.TryGetValue(modName, out ModHost host))
|
||
{
|
||
return host.Assets.Load<T>(assetName);
|
||
}
|
||
|
||
Debug.LogWarning($"Mod '{modName}' is not loaded, or cannot get asset '{assetName}'.");
|
||
return null;
|
||
}
|
||
|
||
public static bool TryGetData<T>(string assetName, out T data) where T : ScriptableObject
|
||
{
|
||
return (data = GetData<T>(assetName)) != null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get data (Scriptable Objects, loaded by data manifest) from the database by its name and type.
|
||
/// </summary>
|
||
/// <param name="assetName">Name of the asset</param>
|
||
public static T GetData<T>(string assetName) where T : ScriptableObject
|
||
{
|
||
if (!IsValidAssetName(assetName))
|
||
{
|
||
Debug.LogWarning($"Asset name '{assetName}' does not follow the 'Type_ModName_AssetName' format.");
|
||
}
|
||
|
||
Type assetType = typeof(T);
|
||
if (Database.TryGetValue(assetType, out Dictionary<string, ScriptableObject> assets))
|
||
{
|
||
if (assets.TryGetValue(assetName, out ScriptableObject asset))
|
||
{
|
||
return asset as T;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"Data Asset '{assetName}' of type '{assetType}' not found in database.");
|
||
return null;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"No assets of type '{assetType}' found in database.");
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public partial class ModManager
|
||
{
|
||
public static readonly Dictionary<string, Type> TypeRegistry = new Dictionary<string, Type>();
|
||
public static string GetTypeID(Type type)
|
||
{
|
||
return type.Namespace!.Replace("Continentis.Mods.", "") + "." + type.Name;
|
||
}
|
||
|
||
public static string GetTypeID(string modName, string classification, string category, string className)
|
||
{
|
||
if (string.IsNullOrEmpty(category))
|
||
{
|
||
return $"{modName}.{classification}.{className}";
|
||
}
|
||
|
||
return $"{modName}.{classification}.{category}.{className}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从一个已加载的Mod中,查找所有指定基类的子类,并将其注册到全局字典中。
|
||
/// </summary>
|
||
/// <param name="host">已加载的ModHost对象</param>
|
||
/// <param name="baseType">要查找的基类,例如 typeof(CardLogicBase)</param>
|
||
public static void RegisterTypesFromMod(ModHost host, Type baseType)
|
||
{
|
||
if (host?.ScriptDomain == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int countBefore = TypeRegistry.Count;
|
||
|
||
// 遍历Mod包含的所有程序集(DLLs)
|
||
foreach (ScriptAssembly assembly in host.ScriptDomain.Assemblies)
|
||
{
|
||
// assembly.RawAssembly 是 uMod 封装的真实 System.Reflection.Assembly 对象
|
||
var typesInAssembly = assembly.RawAssembly.GetTypes()
|
||
.Where(t => baseType.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface);
|
||
|
||
foreach (var type in typesInAssembly)
|
||
{
|
||
string typeID = GetTypeID(type);
|
||
|
||
if (TypeRegistry.TryAdd(typeID, type))
|
||
{
|
||
Debug.Log($"Registered script type '{typeID}' from mod '{host.CurrentMod.NameInfo.ModName}'.");
|
||
}
|
||
else
|
||
{
|
||
// 处理命名冲突:如果不同Mod中存在同名的类,后加载的会被忽略
|
||
Debug.LogWarning($"Duplicate script type name found: '{typeID}'. The existing type from assembly '{TypeRegistry[type.Name].Assembly.FullName}' will be kept.");
|
||
}
|
||
}
|
||
}
|
||
|
||
int countAfter = TypeRegistry.Count;
|
||
if (countAfter > countBefore)
|
||
{
|
||
Debug.Log($"Registered {countAfter - countBefore} new script types deriving from '{baseType.Name}' from mod '{host.CurrentMod.NameInfo.ModName}'.");
|
||
}
|
||
}
|
||
|
||
public static Type GetType(string typeID)
|
||
{
|
||
if (TypeRegistry.TryGetValue(typeID, out Type type))
|
||
{
|
||
return type;
|
||
}
|
||
Debug.LogWarning($"Type '{typeID}' not found in TypeRegistry.");
|
||
return null;
|
||
}
|
||
|
||
public static T CreateInstance<T>(string typeName) where T : class
|
||
{
|
||
Type type = GetType(typeName);
|
||
if (type != null && typeof(T).IsAssignableFrom(type))
|
||
{
|
||
return Activator.CreateInstance(type) as T;
|
||
}
|
||
Debug.LogWarning($"Cannot create instance of type '{typeName}' as it is not found or not assignable to '{typeof(T).Name}'.");
|
||
return null;
|
||
}
|
||
|
||
public static T CreateInstance<T>(string typeName, params object[] parameters) where T : class
|
||
{
|
||
Type type = GetType(typeName);
|
||
if (type != null && typeof(T).IsAssignableFrom(type))
|
||
{
|
||
return Activator.CreateInstance(type, parameters) as T;
|
||
}
|
||
Debug.LogWarning($"Cannot create instance of type '{typeName}' as it is not found or not assignable to '{typeof(T).Name}'.");
|
||
return null;
|
||
}
|
||
}
|
||
} |