using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; using PkmnLib.Dynamic.Libraries; using PkmnLib.Static; using PkmnLib.Static.Utils; namespace PkmnLib.Dynamic.ScriptHandling.Registry; /// <summary> /// A helper data class that's passed through all plugins when initializing a library, so they can /// register their scripts and other data. /// </summary> public class ScriptRegistry { private readonly Dictionary<(ScriptCategory category, StringKey name), Func<Script>> _scriptTypes = new(); private readonly Dictionary<StringKey, Func<IItem, ItemScript>> _itemScriptTypes = new(); private IBattleStatCalculator? _battleStatCalculator; private IDamageCalculator? _damageCalculator; private IMiscLibrary? _miscLibrary; private ICaptureLibrary? _captureLibrary; /// <summary> /// Automatically register all scripts in the given assembly that have the <see cref="ScriptAttribute"/>, and /// inherit from <see cref="Script"/>. /// </summary> public void RegisterAssemblyScripts(Assembly assembly) { var baseType = typeof(Script); foreach (var type in assembly.GetTypes().Where(t => baseType.IsAssignableFrom(t))) { var attribute = type.GetCustomAttribute<ScriptAttribute>(); if (attribute == null) continue; RegisterScriptType(attribute.Category, attribute.Name, type); } var itemBaseType = typeof(ItemScript); foreach (var type in assembly.GetTypes().Where(t => itemBaseType.IsAssignableFrom(t))) { var attribute = type.GetCustomAttribute<ItemScriptAttribute>(); if (attribute == null) continue; RegisterItemScriptType(attribute.Name, type); } } /// <summary> /// Register a script type with the given category and name. /// </summary> [PublicAPI] public void RegisterScriptType(ScriptCategory category, StringKey name, Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); var constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); if (constructor == null) throw new ArgumentException($"Type {type} does not have a parameterless constructor."); // We create a lambda that creates a new instance of the script type. // This is more performant than using Activator.CreateInstance. _scriptTypes[(category, name)] = Expression.Lambda<Func<Script>>(Expression.New(constructor)).Compile(); } /// <summary> /// Register an item script type with the given name. /// </summary> [PublicAPI] public void RegisterItemScriptType(StringKey name, Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); var constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, [typeof(IItem)], null); if (constructor == null) throw new ArgumentException($"Type {type} does not have a constructor that takes an IItem."); // We create a lambda that creates a new instance of the script type. // This is more performant than using Activator.CreateInstance. var parameterExpression = Expression.Parameter(typeof(IItem), "item"); var newExpression = Expression.New(constructor, parameterExpression); _itemScriptTypes[name] = Expression.Lambda<Func<IItem, ItemScript>>(newExpression, parameterExpression).Compile(); } /// <summary> /// Register a battle stat calculator. /// </summary> public void RegisterBattleStatCalculator<T>(T battleStatCalculator) where T : IBattleStatCalculator => _battleStatCalculator = battleStatCalculator; /// <summary> /// Register a damage calculator. /// </summary> public void RegisterDamageCalculator<T>(T damageCalculator) where T : IDamageCalculator => _damageCalculator = damageCalculator; /// <summary> /// Register a misc library. /// </summary> public void RegisterMiscLibrary<T>(T miscLibrary) where T : IMiscLibrary => _miscLibrary = miscLibrary; /// <summary> /// Register a capture library. /// </summary> public void RegisterCaptureLibrary<T>(T captureLibrary) where T : ICaptureLibrary => _captureLibrary = captureLibrary; internal IReadOnlyDictionary<(ScriptCategory category, StringKey name), Func<Script>> ScriptTypes => _scriptTypes; internal IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> ItemScriptTypes => _itemScriptTypes; internal IBattleStatCalculator? BattleStatCalculator => _battleStatCalculator; internal IDamageCalculator? DamageCalculator => _damageCalculator; internal IMiscLibrary? MiscLibrary => _miscLibrary; internal ICaptureLibrary? CaptureLibrary => _captureLibrary; }