From 11cac69eba52442f150f4ce264ecff4754e1bab3 Mon Sep 17 00:00:00 2001 From: Reetus <6239195+Reetus@users.noreply.github.com> Date: Thu, 20 Feb 2020 18:08:01 +0800 Subject: [PATCH] Spawner wouldn't spawn Types with optional parameters in ctor due to parameter length check (#92) --- Projects/Scripts/Engines/Spawner/Spawner.cs | 70 +++++++------ Projects/Server/Utilities/ActivatorUtil.cs | 103 +++++++++++++++----- 2 files changed, 118 insertions(+), 55 deletions(-) diff --git a/Projects/Scripts/Engines/Spawner/Spawner.cs b/Projects/Scripts/Engines/Spawner/Spawner.cs index c1f0df49d..1c6fd374f 100644 --- a/Projects/Scripts/Engines/Spawner/Spawner.cs +++ b/Projects/Scripts/Engines/Spawner/Spawner.cs @@ -4,6 +4,7 @@ using System.IO; using System.Reflection; using Server.Commands; using Server.Items; +using Server.Utilities; using CPA = Server.CommandPropertyAttribute; namespace Server.Mobiles @@ -472,49 +473,58 @@ namespace Server.Mobiles else paramargs = entry.Parameters.Trim().Split(' '); - ConstructorInfo[] ctors = type.GetConstructors(); - - for (int i = 0; i < ctors.Length; ++i) + if (paramargs.Length == 0) { - ConstructorInfo ctor = ctors[i]; + o = ActivatorUtil.CreateInstance(type, ci => Add.IsConstructible(ci, AccessLevel.Developer)); + } + else + { + ConstructorInfo[] ctors = type.GetConstructors(); - if (Add.IsConstructible(ctor, AccessLevel.Developer)) + for (int i = 0; i < ctors.Length; ++i) { - ParameterInfo[] paramList = ctor.GetParameters(); + ConstructorInfo ctor = ctors[i]; - if (paramargs.Length == paramList.Length) + if (Add.IsConstructible(ctor, AccessLevel.Developer)) { - object[] paramValues = Add.ParseValues(paramList, paramargs); + ParameterInfo[] paramList = ctor.GetParameters(); - if (paramValues != null) + if (paramargs.Length == paramList.Length) { - o = ctor.Invoke(paramValues); - for (int j = 0; j < realProps.Length; j++) - if (realProps[j] != null) - { - object toSet = null; - string result = Properties.ConstructFromString(realProps[j].PropertyType, o, - props[j, 1], ref toSet); - if (result == null) - { - realProps[j].SetValue(o, toSet, null); - } - else - { - flags = EntryFlags.InvalidProps; + object[] paramValues = Add.ParseValues(paramList, paramargs); - (o as ISpawnable)?.Delete(); - - return false; - } - } - - break; + if (paramValues != null) + { + o = ctor.Invoke(paramValues); + break; + } } } } } + for (int i = 0; i < realProps.Length; i++) + { + if (realProps[i] != null) + { + object toSet = null; + string result = Properties.ConstructFromString(realProps[i].PropertyType, o, props[i, 1], ref toSet); + + if (result == null) + { + realProps[i].SetValue(o, toSet, null); + } + else + { + flags = EntryFlags.InvalidProps; + + (o as ISpawnable)?.Delete(); + + return false; + } + } + } + if (o is Mobile m) { Spawned.Add(m, entry); diff --git a/Projects/Server/Utilities/ActivatorUtil.cs b/Projects/Server/Utilities/ActivatorUtil.cs index 99eca9892..32a92a71c 100644 --- a/Projects/Server/Utilities/ActivatorUtil.cs +++ b/Projects/Server/Utilities/ActivatorUtil.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -6,68 +7,120 @@ namespace Server.Utilities { public static class ActivatorUtil { + public static ConstructorInfo GetConstructor(Type type, Predicate predicate = null) + { + ConstructorInfo emptyCtor = type.GetConstructor(Type.EmptyTypes); - public static ConstructorInfo GetConstructor(Type type) - { - try + if (emptyCtor != null && predicate?.Invoke(emptyCtor) != false) { - return type.GetConstructor(Type.EmptyTypes) - ?? type.GetConstructors().Single(info => info.GetParameters().All(x => x.IsOptional)) - ?? throw new TypeInitializationException(type.ToString(), new Exception($"There is no empty/default constructor for {type}")); + return emptyCtor; } - catch (Exception e) + + ConstructorInfo optionalCtor = type.GetConstructors().SingleOrDefault(info => + predicate?.Invoke(info) != false && info.GetParameters().All(x => x.IsOptional)); + + if (optionalCtor != null) { - throw new TypeInitializationException(type.ToString(), e); + return optionalCtor; } + + throw new TypeInitializationException(type.ToString(), + new Exception($"There is no empty/default constructor for {type} that matches predicate.")); } - public static ConstructorInfo GetConstructor(Type type, params Type[] args) + + public static ConstructorInfo GetConstructor(Type type, Predicate predicate, params Type[] args) { try { - return args.All(x => x != null) ? type.GetConstructor(args) : null ?? type.GetConstructors().Single(info => + ConstructorInfo ctor; + + if (args.All(x => x != null)) + { + ctor = type.GetConstructor(args); + + if (ctor != null && predicate?.Invoke(ctor) != false) { - var paramList = info.GetParameters().ToList(); + return ctor; + } + } + else + { + ctor = type.GetConstructors().SingleOrDefault(info => + { + if (predicate?.Invoke(info) == false) + { + return false; + } + + List paramList = info.GetParameters().ToList(); + // If more args are given than parameters, skip. if (args.Length > paramList.Count) + { return false; + } + // check all given args map to params. for (int i = 0; i < args.Length; i++) { // if a null reference is passed, but the type is not nullable - if ((args[i] == null && paramList[i].ParameterType.IsValueType) - // or if an arg is not null and is not assignable to the parameter type, skip. - || !(args[i] == null || paramList[i].ParameterType.IsAssignableFrom(args[i]))) + if (args[i] == null && paramList[i].ParameterType.IsValueType + // or if an arg is not null and is not assignable to the parameter type, skip. + || !(args[i] == null || paramList[i].ParameterType.IsAssignableFrom(args[i]))) + { return false; + } } + // If there are more parameters, check if they any are not optional, if any are not, skip. // Otherwise all checks have passed. We have found a match - return args.Length <= paramList.Count || paramList.GetRange(args.Length, paramList.Count - args.Length).All(x => x.IsOptional); - }) ?? throw new Exception($"There is no empty/default constructor for {type}"); + return args.Length <= paramList.Count || paramList.GetRange(args.Length, paramList.Count - args.Length) + .All(x => x.IsOptional); + }); + + if (ctor != null) + { + return ctor; + } + } + + throw new Exception($"There is no empty/default constructor for {type} that matches predicate."); } catch (Exception e) { throw new TypeInitializationException(type.ToString(), e); } } - public static object CreateInstance(Type type) + + public static object CreateInstance(Type type, Predicate constructorPredicate = null) { - var cctor = GetConstructor(type); - var args = cctor.GetParameters(); + ConstructorInfo cctor = GetConstructor(type, constructorPredicate); + ParameterInfo[] args = cctor.GetParameters(); + if (args.Length == 0) + { return cctor.Invoke(Type.EmptyTypes); - var argList = new object[args.Length]; + } + + object[] argList = new object[args.Length]; Array.Fill(argList, Type.Missing); return cctor.Invoke(argList); } - public static object CreateInstance(Type type, params object[] args) + + public static object CreateInstance(Type type, Predicate constructorPredicate = null, + params object[] args) { if (args == null || args.Length == 0) - return CreateInstance(type); - var cctor = GetConstructor(type, args.Select(x => x?.GetType()).ToArray()); + { + return CreateInstance(type, constructorPredicate); + } + + ConstructorInfo cctor = GetConstructor(type, constructorPredicate, args.Select(x => x?.GetType()).ToArray()); return cctor.Invoke(args); } - public static T CreateInstance() => (T)CreateInstance(typeof(T)); - public static T CreateInstance(params object[] args) => (T)CreateInstance(typeof(T), args); + public static object CreateInstance(Type type, params object[] args) => CreateInstance(type, null, args); + public static T CreateInstance(Predicate constructorPredicate = null) => (T)CreateInstance(typeof(T), constructorPredicate); + public static T CreateInstance(params object[] args) => (T)CreateInstance(typeof(T), null, args); } }