diff --git a/Projects/UOContent.Tests/Tests/Commands/Objects/ObjectDiscoveryTests.cs b/Projects/UOContent.Tests/Tests/Commands/Objects/ObjectDiscoveryTests.cs new file mode 100644 index 000000000..d36f8bb8a --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Commands/Objects/ObjectDiscoveryTests.cs @@ -0,0 +1,19 @@ +using Server.Commands; +using Server.Items; +using Xunit; + +namespace UOContent.Tests.Commands.Objects; + +[Collection("Sequential UOContent Tests")] +public class ObjectDiscoveryTests +{ + [Fact] + public void Discover_includes_concrete_constructibles_and_excludes_abstract() + { + var types = ObjectIntrospection.DiscoverConstructibleTypes(); + + Assert.Contains(typeof(Katana), types); + Assert.Contains(typeof(Runebook), types); + Assert.DoesNotContain(typeof(BaseWeapon), types); // abstract + } +} diff --git a/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs b/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs index f4d076f97..f0a11b31c 100644 --- a/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs +++ b/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs @@ -184,4 +184,45 @@ public static class ObjectIntrospection return lines; } + + public static List DiscoverConstructibleTypes() + { + var results = new List(); + + foreach (var asm in AssemblyHandler.Assemblies) + { + foreach (var type in AssemblyHandler.GetTypeCache(asm).Types) + { + if (type.IsAbstract) + { + continue; + } + + if (!typeof(Item).IsAssignableFrom(type) && !typeof(Mobile).IsAssignableFrom(type)) + { + continue; + } + + if (HasConstructibleCtor(type)) + { + results.Add(type); + } + } + } + + return results; + } + + private static bool HasConstructibleCtor(Type type) + { + foreach (var ctor in type.GetConstructors()) + { + if (Attributes.IsConstructible(ctor, AccessLevel.Developer)) + { + return true; + } + } + + return false; + } }