From f4a87a8629dcd94149047aa9c8ba3211b820b246 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:21:29 -0800 Subject: [PATCH] fix: Fixes adding items with ambiguous type lookup (#2307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix [add command failing with ambiguous type names + refactor for performance ### Problem [add blight would fail with "No type with that name was found" because multiple types contain "blight" (e.g., Server.Items.Blight, Server.Ethics.Evil.Blight, Server.Items.BlightGrippedLongbow, Server.Items.QuiverOfBlight). The old code only succeeded when exactly one type matched the search regardless of constructability and inheriting Mobi les/Items. ### Solution Exact match takes priority: If a type's name exactly equals the search string (case-insensitive), use it directly. Otherwise, show the AddGump with all partial matches. - [add blight → Creates Blight (exact name match) - [add bligh → Shows gump with Blight, BlightGrippedLongbow, etc. ### Refactoring - CommandEventArgs context: Added GetContext/SetContext to pass resolved type through the command chain without method signature changes - Removed TrySetupTarget duplication: Validation now happens only in ValidateArgs, eliminating redundant code paths - Split type matching: - ExactMatch(string) → Returns Type for exact name match (used by [add) - MatchEmptyCtor(string) → Returns ConstructorInfo[] for gump display (empty-callable constructors only) ### Memory & Performance Improvements | Optimization | Benefit | |-------------------------------|----------------------------------------------------------------------------------------------| | _mobileItemTypes cache | Filters Mobile/Item types once per assembly, reused on all subsequent searches | | ReadOnlySpan for args | Avoids string[] heap allocations when slicing arguments | | ValueStringBuilder | Stack-allocated string building, avoids StringBuilder heap allocation | | Single type resolution | Type resolved once in ValidateArgs, passed via context to Execute (was resolved 2-3x before) | --- Projects/Server/Commands.cs | 21 + Projects/UOContent/Commands/DragEffects.cs | 8 +- .../Commands/Generic/Commands/Commands.cs | 64 +- .../Implementors/BaseCommandImplementor.cs | 7 - .../Implementors/SingleCommandImplementor.cs | 2 +- Projects/UOContent/Commands/Handlers.cs | 4 +- .../UOContent/Commands/Object Creation/Add.cs | 1228 +++++++++-------- .../Commands/Object Creation/AddGump.cs | 175 ++- .../UOContent/Engines/ConPVP/DuelContext.cs | 2 +- .../Engines/ML Quests/MLQuestSystem.cs | 4 +- .../Items/Stones/GamblingStone.cs | 2 +- .../UOContent/World Saves/SaveCommands.cs | 4 +- 12 files changed, 837 insertions(+), 684 deletions(-) diff --git a/Projects/Server/Commands.cs b/Projects/Server/Commands.cs index 6819dfe91..41547f63e 100644 --- a/Projects/Server/Commands.cs +++ b/Projects/Server/Commands.cs @@ -41,6 +41,8 @@ public class CommandEventArgs public string[] Arguments { get; } + private Dictionary _context; + public int Length => Arguments.Length; public string GetString(int index) => index < 0 || index >= Arguments.Length ? "" : Arguments[index]; @@ -57,6 +59,25 @@ public class CommandEventArgs public TimeSpan GetTimeSpan(int index) => index < 0 || index >= Arguments.Length ? TimeSpan.Zero : Utility.ToTimeSpan(Arguments[index]); + + public bool GetContext(string key, out T t) + { + if (_context != null && _context.TryGetValue(key, out var value) && value is T tValue) + { + t = tValue; + return true; + } + + t = default; + return false; + } + + public T SetContext(string key, T value) + { + _context ??= new Dictionary(); + _context[key] = value; + return value; + } } public static partial class EventSink diff --git a/Projects/UOContent/Commands/DragEffects.cs b/Projects/UOContent/Commands/DragEffects.cs index dac60f8d0..4f4d2b96f 100644 --- a/Projects/UOContent/Commands/DragEffects.cs +++ b/Projects/UOContent/Commands/DragEffects.cs @@ -15,11 +15,11 @@ namespace Server.Commands { if (Mobile.DragEffects) { - e.Mobile.SendMessage($"Drag effects are currently enabled."); + e.Mobile.SendMessage("Drag effects are currently enabled."); } else { - e.Mobile.SendMessage($"Drag effects are currently disabled."); + e.Mobile.SendMessage("Drag effects are currently disabled."); } } else @@ -28,11 +28,11 @@ namespace Server.Commands if (Mobile.DragEffects) { - e.Mobile.SendMessage($"Drag effects have been enabled."); + e.Mobile.SendMessage("Drag effects have been enabled."); } else { - e.Mobile.SendMessage($"Drag effects have been disabled."); + e.Mobile.SendMessage("Drag effects have been disabled."); } } } diff --git a/Projects/UOContent/Commands/Generic/Commands/Commands.cs b/Projects/UOContent/Commands/Generic/Commands/Commands.cs index 865699db1..81d85fc0a 100644 --- a/Projects/UOContent/Commands/Generic/Commands/Commands.cs +++ b/Projects/UOContent/Commands/Generic/Commands/Commands.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Server.Accounting; using Server.Engines.Help; using Server.Factions; @@ -511,36 +512,39 @@ namespace Server.Commands.Generic public override bool ValidateArgs(BaseCommandImplementor impl, CommandEventArgs e) { - if (e.Length >= 1) - { - var t = AssemblyHandler.FindTypeByName(e.GetString(0)); - - if (t == null) - { - e.Mobile.SendMessage("No type with that name was found."); - - var match = e.GetString(0).Trim(); - - if (match.Length < 3) - { - e.Mobile.SendMessage("Invalid search string."); - e.Mobile.SendGump(new AddGump(match, 0, Type.EmptyTypes, false)); - } - else - { - e.Mobile.SendGump(new AddGump(match, 0, AddGump.Match(match), true)); - } - } - else - { - return true; - } - } - else + if (e.Length < 1) { e.Mobile.SendGump(new CategorizedAddGump(e.Mobile)); + return false; } + var match = e.GetString(0).Trim(); + if (e.GetContext("exactMatch", out var exactMatch)) + { + return true; + } + + exactMatch = AddGump.ExactMatch(match); + if (exactMatch != null) + { + e.SetContext("exactMatch", exactMatch); + return true; + } + + if (match.Length < 3) + { + e.Mobile.SendMessage("Invalid search string."); + e.Mobile.SendGump(new AddGump(match, 0, [], false)); + return false; + } + + if (!e.GetContext("matches", out var matches)) + { + matches = e.SetContext("matches", AddGump.MatchEmptyCtor(match)); + } + + e.Mobile.SendMessage("No type with that name was found."); + e.Mobile.SendGump(new AddGump(match, 0, matches, true)); return false; } @@ -558,7 +562,13 @@ namespace Server.Commands.Generic _ => new Point3D(ip) }; - Add.Invoke(e.Mobile, p, p, e.Arguments); + if (e.GetContext("exactMatch", out var exactMatch)) + { + Add.Invoke(e.Mobile, p, p, exactMatch, e.Arguments.AsSpan(1)); + return; + } + + e.Mobile.SendMessage("No type with that name was found."); } } diff --git a/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs b/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs index bce4737a3..a8cae3741 100644 --- a/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs +++ b/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs @@ -213,8 +213,6 @@ namespace Server.Commands.Generic public void RunCommand(Mobile from, object obj, BaseCommand command, string[] args) { - // try - // { var e = new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args); if (!command.ValidateArgs(this, e)) @@ -256,11 +254,6 @@ namespace Server.Commands.Generic } command.Flush(from, flushToLog); - // } - // catch ( Exception ex ) - // { - // from.SendMessage( ex.Message ); - // } } public virtual void Process(Mobile from, BaseCommand command, string[] args) diff --git a/Projects/UOContent/Commands/Generic/Implementors/SingleCommandImplementor.cs b/Projects/UOContent/Commands/Generic/Implementors/SingleCommandImplementor.cs index 3220cd28b..033cb73f5 100644 --- a/Projects/UOContent/Commands/Generic/Implementors/SingleCommandImplementor.cs +++ b/Projects/UOContent/Commands/Generic/Implementors/SingleCommandImplementor.cs @@ -36,7 +36,7 @@ namespace Server.Commands.Generic { e.Mobile.SendMessage("You do not have access to that command."); } - else if (command.ValidateArgs(this, e)) + else { Process(e.Mobile, command, e.Arguments); } diff --git a/Projects/UOContent/Commands/Handlers.cs b/Projects/UOContent/Commands/Handlers.cs index e98ab1084..cfeb8bd5a 100644 --- a/Projects/UOContent/Commands/Handlers.cs +++ b/Projects/UOContent/Commands/Handlers.cs @@ -665,11 +665,11 @@ namespace Server.Commands if (m.AutoPageNotify) { - m.SendMessage($"Your auto-page-notify has been turned on."); + m.SendMessage("Your auto-page-notify has been turned on."); } else { - m.SendMessage($"Your auto-page-notify has been turned off."); + m.SendMessage("Your auto-page-notify has been turned off."); } } diff --git a/Projects/UOContent/Commands/Object Creation/Add.cs b/Projects/UOContent/Commands/Object Creation/Add.cs index 240d8dbd4..b34af5bac 100644 --- a/Projects/UOContent/Commands/Object Creation/Add.cs +++ b/Projects/UOContent/Commands/Object Creation/Add.cs @@ -3,697 +3,761 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Text; +using Server.Gumps; using Server.Items; +using Server.Text; using static Server.Attributes; using static Server.Types; -namespace Server.Commands -{ - public static class Add - { - public static void Configure() - { - CommandSystem.Register("Tile", AccessLevel.GameMaster, Tile_OnCommand); - CommandSystem.Register("TileRXYZ", AccessLevel.GameMaster, TileRXYZ_OnCommand); - CommandSystem.Register("TileXYZ", AccessLevel.GameMaster, TileXYZ_OnCommand); - CommandSystem.Register("TileZ", AccessLevel.GameMaster, TileZ_OnCommand); - CommandSystem.Register("TileAvg", AccessLevel.GameMaster, TileAvg_OnCommand); +namespace Server.Commands; - CommandSystem.Register("Outline", AccessLevel.GameMaster, Outline_OnCommand); - CommandSystem.Register("OutlineRXYZ", AccessLevel.GameMaster, OutlineRXYZ_OnCommand); - CommandSystem.Register("OutlineXYZ", AccessLevel.GameMaster, OutlineXYZ_OnCommand); - CommandSystem.Register("OutlineZ", AccessLevel.GameMaster, OutlineZ_OnCommand); - CommandSystem.Register("OutlineAvg", AccessLevel.GameMaster, OutlineAvg_OnCommand); +public static class Add +{ + public static void Configure() + { + CommandSystem.Register("Tile", AccessLevel.GameMaster, Tile_OnCommand); + CommandSystem.Register("TileRXYZ", AccessLevel.GameMaster, TileRXYZ_OnCommand); + CommandSystem.Register("TileXYZ", AccessLevel.GameMaster, TileXYZ_OnCommand); + CommandSystem.Register("TileZ", AccessLevel.GameMaster, TileZ_OnCommand); + CommandSystem.Register("TileAvg", AccessLevel.GameMaster, TileAvg_OnCommand); + + CommandSystem.Register("Outline", AccessLevel.GameMaster, Outline_OnCommand); + CommandSystem.Register("OutlineRXYZ", AccessLevel.GameMaster, OutlineRXYZ_OnCommand); + CommandSystem.Register("OutlineXYZ", AccessLevel.GameMaster, OutlineXYZ_OnCommand); + CommandSystem.Register("OutlineZ", AccessLevel.GameMaster, OutlineZ_OnCommand); + CommandSystem.Register("OutlineAvg", AccessLevel.GameMaster, OutlineAvg_OnCommand); + } + + public static void Invoke( + Mobile from, Point3D start, Point3D end, string[] args, List packs = null, + bool outline = false, bool mapAvg = false + ) + { + var type = AddGump.ExactMatch(args[0]); + + if (type == null) + { + from.SendMessage("No type with that name was found."); + return; } - public static void Invoke( - Mobile from, Point3D start, Point3D end, string[] args, List packs = null, - bool outline = false, bool mapAvg = false - ) + Invoke(from, start, end, type, args.AsSpan(1), packs, outline, mapAvg); + } + + public static void Invoke(Mobile from, Point3D start, Point3D end, ConstructorInfo ctor) + { + using var sb = ValueStringBuilder.Create(); + + sb.Append($"{from.AccessLevel} {CommandLogging.Format(from)} building "); + + if (start == end) { - var sb = new StringBuilder(); + sb.Append($"at {start} in {from.Map}"); + } + else + { + sb.Append($"from {start} to {end} in {from.Map}"); + } - sb.Append($"{from.AccessLevel} {CommandLogging.Format(from)} building "); + sb.Append($": {ctor.DeclaringType!.Name}"); - if (start == end) + CommandLogging.WriteLine(from, sb.ToString()); + + var watch = new Stopwatch(); + watch.Start(); + + Utility.FixPoints(ref start, ref end); + + // Handle optional parameters + var paramList = ctor.GetParameters(); + var parms = paramList.Length == 0 ? Array.Empty() : new object[paramList.Length]; + for (var i = 0; i < parms.Length; i++) + { + parms[i] = Type.Missing; + } + + var built = Build(from, start, end, ctor, parms, null, null, null); + + if (built <= 0) + { + SendUsage(ctor, from); + return; + } + + watch.Stop(); + + if (built == 1) + { + from.SendMessage($"{built} object generated in {watch.Elapsed.TotalSeconds:F2} seconds."); + } + else + { + from.SendMessage($"{built} objects generated in {watch.Elapsed.TotalSeconds:F2} seconds."); + } + } + + public static void Invoke( + Mobile from, Point3D start, Point3D end, Type type, + ReadOnlySpan args = default, List packs = null, bool outline = false, bool mapAvg = false + ) + { + using var sb = ValueStringBuilder.Create(); + + sb.Append($"{from.AccessLevel} {CommandLogging.Format(from)} building "); + + if (start == end) + { + sb.Append($"at {start} in {from.Map}"); + } + else + { + sb.Append($"from {start} to {end} in {from.Map}"); + } + + sb.Append($": {type.Name}"); + + string[,] props = null; + + var setArgsIndex = -1; + for (var i = 0; i < args.Length; ++i) + { + sb.Append($" \"{args[i]}\""); + + if (setArgsIndex != -1 || !args[i].InsensitiveEquals("set")) { - sb.Append($"at {start} in {from.Map}"); - } - else - { - sb.Append($"from {start} to {end} in {from.Map}"); + continue; } - sb.Append(':'); + setArgsIndex = i; + var remains = args.Length - i - 1; - for (var i = 0; i < args.Length; ++i) + if (remains < 2) { - sb.Append($" \"{args[i]}\""); + continue; } - CommandLogging.WriteLine(from, sb.ToString()); + props = new string[remains / 2, 2]; - var name = args[0]; + remains /= 2; - FixArgs(ref args); - - string[,] props = null; - - for (var i = 0; i < args.Length; ++i) + for (var j = 0; j < remains; ++j) { - if (args[i].InsensitiveEquals("set")) + props[j, 0] = args[i + j * 2 + 1]; + props[j, 1] = args[i + j * 2 + 2]; + } + } + + CommandLogging.WriteLine(from, sb.ToString()); + + ReadOnlySpan ctorArgs = setArgsIndex != -1 ? args[..setArgsIndex] : args; + + var watch = new Stopwatch(); + watch.Start(); + + var built = BuildObjects(from, type, start, end, ctorArgs, props, packs, outline, mapAvg); + + if (built <= 0) + { + SendUsage(type, from); + return; + } + + watch.Stop(); + + if (built == 1) + { + from.SendMessage($"{built} object generated in {watch.Elapsed.TotalSeconds:F2} seconds."); + } + else + { + from.SendMessage($"{built} objects generated in {watch.Elapsed.TotalSeconds:F2} seconds."); + } + } + + public static int BuildObjects( + Mobile from, Type type, Point3D start, Point3D end, ReadOnlySpan args, string[,] props, + List packs, bool outline = false, bool mapAvg = false + ) + { + Utility.FixPoints(ref start, ref end); + + PropertyInfo[] realProps = null; + + if (props != null) + { + realProps = new PropertyInfo[props.GetLength(0)]; + + var allProps = + type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); + + for (var i = 0; i < realProps.Length; ++i) + { + var propName = props[i, 0]; + var thisProp = Properties.GetPropertyInfoByName(from, allProps, propName, PropertyAccess.Write, out var failReason); + + if (failReason == null) { - var remains = args.Length - i - 1; - - if (remains >= 2) - { - props = new string[remains / 2, 2]; - - remains /= 2; - - for (var j = 0; j < remains; ++j) - { - props[j, 0] = args[i + j * 2 + 1]; - props[j, 1] = args[i + j * 2 + 2]; - } - - FixSetString(ref args, i); - } - - break; - } - } - - var type = AssemblyHandler.FindTypeByName(name); - - if (!IsEntity(type)) - { - from.SendMessage("No type with that name was found."); - return; - } - - var watch = new Stopwatch(); - watch.Start(); - - var built = BuildObjects(from, type, start, end, args, props, packs, outline, mapAvg); - - if (built > 0) - { - watch.Stop(); - if (built == 1) - { - from.SendMessage($"{built} object generated in {watch.Elapsed.TotalSeconds:F2} seconds."); + realProps[i] = thisProp; } else { - from.SendMessage($"{built} objects generated in {watch.Elapsed.TotalSeconds:F2} seconds."); + from.SendMessage(failReason); } } - else + } + + var ctors = type.GetConstructors(); + + for (var i = 0; i < ctors.Length; ++i) + { + var ctor = ctors[i]; + + if (!IsConstructible(ctor, from.AccessLevel)) { - SendUsage(type, from); + continue; } - } - public static void FixSetString(ref string[] args, int index) - { - var old = args; - args = GC.AllocateUninitializedArray(index); - - Array.Copy(old, 0, args, 0, index); - } - - public static void FixArgs(ref string[] args) - { - var old = args; - args = GC.AllocateUninitializedArray(args.Length - 1); - - Array.Copy(old, 1, args, 0, args.Length); - } - - public static int BuildObjects( - Mobile from, Type type, Point3D start, Point3D end, string[] args, string[,] props, - List packs, bool outline = false, bool mapAvg = false - ) - { - Utility.FixPoints(ref start, ref end); - - PropertyInfo[] realProps = null; - - if (props != null) + // Handle optional constructors + var paramList = ctor.GetParameters(); + var totalParams = 0; + for (var j = 0; j < paramList.Length; j++) { - realProps = new PropertyInfo[props.GetLength(0)]; - - var allProps = - type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); - - for (var i = 0; i < realProps.Length; ++i) + if (!paramList[j].HasDefaultValue) { - var propName = props[i, 0]; - var thisProp = Properties.GetPropertyInfoByName(from, allProps, propName, PropertyAccess.Write, out var failReason); - - if (failReason == null) - { - realProps[i] = thisProp; - } - else - { - from.SendMessage(failReason); - } + totalParams++; } } - var ctors = type.GetConstructors(); - - for (var i = 0; i < ctors.Length; ++i) + if (args.Length >= totalParams && args.Length <= paramList.Length) { - var ctor = ctors[i]; + var paramValues = ParseValues(paramList, args); - if (!IsConstructible(ctor, from.AccessLevel)) + if (paramValues == null) { continue; } - // Handle optional constructors - var paramList = ctor.GetParameters(); - var totalParams = 0; - for (var j = 0; j < paramList.Length; j++) + var built = Build(from, start, end, ctor, paramValues, props, realProps, packs, outline, mapAvg); + + if (built > 0) { - if (!paramList[j].HasDefaultValue) - { - totalParams++; - } + return built; + } + } + } + + return 0; + } + + public static object[] ParseValues(ParameterInfo[] paramList, ReadOnlySpan args) + { + var values = new object[paramList.Length]; + + for (int i = 0, a = 0; i < paramList.Length; i++) + { + var param = paramList[i]; + TryParse(param.ParameterType, a < args.Length ? args[a++] : null, out var value); + + if (value != null) + { + values[i] = value; + } + else if (param.HasDefaultValue) + { + values[i] = Type.Missing; + } + else + { + return null; + } + } + + return values; + } + + public static IEntity Build( + Mobile from, ConstructorInfo ctor, object[] values, string[,] props, + PropertyInfo[] realProps, ref bool sendError + ) + { + var built = ctor.Invoke(values); + + if (realProps != null) + { + var hadError = false; + + for (var i = 0; i < realProps.Length; ++i) + { + if (realProps[i] == null) + { + continue; } - if (args.Length >= totalParams && args.Length <= paramList.Length) + var result = + Properties.InternalSetValue(from, built, built, realProps[i], props[i, 1], props[i, 1], false); + + if (result != "Property has been set.") { - var paramValues = ParseValues(paramList, args); - - if (paramValues == null) + if (sendError) { - continue; + from.SendMessage(result); } - var built = Build(from, start, end, ctor, paramValues, props, realProps, packs, outline, mapAvg); - - if (built > 0) - { - return built; - } + hadError = true; } } - return 0; + if (hadError) + { + sendError = false; + } } - public static object[] ParseValues(ParameterInfo[] paramList, string[] args) + return (IEntity)built; + } + + public static int Build( + Mobile from, Point3D start, Point3D end, ConstructorInfo ctor, object[] values, + string[,] props, PropertyInfo[] realProps, List packs, bool outline = false, bool mapAvg = false + ) + { + try { - var values = new object[paramList.Length]; + var map = from.Map; - for (int i = 0, a = 0; i < paramList.Length; i++) + var width = end.X - start.X + 1; + var height = end.Y - start.Y + 1; + + if (outline && (width < 3 || height < 3)) { - var param = paramList[i]; - TryParse(param.ParameterType, a < args.Length ? args[a++] : null, out var value); - - if (value != null) - { - values[i] = value; - } - else if (param.HasDefaultValue) - { - values[i] = Type.Missing; - } - else - { - return null; - } + outline = false; } - return values; - } + int objectCount; - public static IEntity Build( - Mobile from, ConstructorInfo ctor, object[] values, string[,] props, - PropertyInfo[] realProps, ref bool sendError - ) - { - var built = ctor.Invoke(values); - - if (realProps != null) + if (packs != null) { - var hadError = false; + objectCount = packs.Count; + } + else if (outline) + { + objectCount = (width + height - 2) * 2; + } + else + { + objectCount = width * height; + } - for (var i = 0; i < realProps.Length; ++i) + if (objectCount >= 20) + { + from.SendMessage($"Constructing {objectCount} objects, please wait."); + } + + var sendError = true; + + var sb = new StringBuilder(); + sb.Append("Serials: "); + + if (packs != null) + { + for (var i = 0; i < packs.Count; ++i) { - if (realProps[i] == null) + var built = Build(from, ctor, values, props, realProps, ref sendError); + + sb.Append($"{built.Serial}; "); + + if (built is Item item) { - continue; + packs[i].DropItem(item); } - - var result = - Properties.InternalSetValue(from, built, built, realProps[i], props[i, 1], props[i, 1], false); - - if (result != "Property has been set.") + else if (built is Mobile m) { - if (sendError) + m.MoveToWorld(new Point3D(start.X, start.Y, start.Z), map); + } + } + } + else + { + var z = start.Z; + + for (var x = start.X; x <= end.X; ++x) + { + for (var y = start.Y; y <= end.Y; ++y) + { + if (outline && x != start.X && x != end.X && y != start.Y && y != end.Y) { - from.SendMessage(result); + continue; } - hadError = true; - } - } + if (mapAvg) + { + z = map.GetAverageZ(x, y); + } - if (hadError) - { - sendError = false; - } - } - - return (IEntity)built; - } - - public static int Build( - Mobile from, Point3D start, Point3D end, ConstructorInfo ctor, object[] values, - string[,] props, PropertyInfo[] realProps, List packs, bool outline = false, bool mapAvg = false - ) - { - try - { - var map = from.Map; - - var width = end.X - start.X + 1; - var height = end.Y - start.Y + 1; - - if (outline && (width < 3 || height < 3)) - { - outline = false; - } - - int objectCount; - - if (packs != null) - { - objectCount = packs.Count; - } - else if (outline) - { - objectCount = (width + height - 2) * 2; - } - else - { - objectCount = width * height; - } - - if (objectCount >= 20) - { - from.SendMessage($"Constructing {objectCount} objects, please wait."); - } - - var sendError = true; - - var sb = new StringBuilder(); - sb.Append("Serials: "); - - if (packs != null) - { - for (var i = 0; i < packs.Count; ++i) - { var built = Build(from, ctor, values, props, realProps, ref sendError); sb.Append($"{built.Serial}; "); if (built is Item item) { - packs[i].DropItem(item); + item.MoveToWorld(new Point3D(x, y, z), map); } else if (built is Mobile m) { - m.MoveToWorld(new Point3D(start.X, start.Y, start.Z), map); + m.MoveToWorld(new Point3D(x, y, z), map); } } } - else - { - var z = start.Z; - - for (var x = start.X; x <= end.X; ++x) - { - for (var y = start.Y; y <= end.Y; ++y) - { - if (outline && x != start.X && x != end.X && y != start.Y && y != end.Y) - { - continue; - } - - if (mapAvg) - { - z = map.GetAverageZ(x, y); - } - - var built = Build(from, ctor, values, props, realProps, ref sendError); - - sb.Append($"{built.Serial}; "); - - if (built is Item item) - { - item.MoveToWorld(new Point3D(x, y, z), map); - } - else if (built is Mobile m) - { - m.MoveToWorld(new Point3D(x, y, z), map); - } - } - } - } - - CommandLogging.WriteLine(from, sb.ToString()); - - return objectCount; - } - catch (Exception ex) - { - Console.WriteLine(ex); - return 0; } + + CommandLogging.WriteLine(from, sb.ToString()); + + return objectCount; } - - public static void SendUsage(Type type, Mobile from) + catch (Exception ex) { - var ctors = type.GetConstructors(); - var foundCtor = false; + Console.WriteLine(ex); + return 0; + } + } - for (var i = 0; i < ctors.Length; ++i) + public static void SendUsage(Type type, Mobile from) + { + var ctors = type.GetConstructors(); + var foundCtor = false; + + for (var i = 0; i < ctors.Length; ++i) + { + var ctor = ctors[i]; + + if (!IsConstructible(ctor, from.AccessLevel)) { - var ctor = ctors[i]; - - if (!IsConstructible(ctor, from.AccessLevel)) - { - continue; - } - - if (!foundCtor) - { - foundCtor = true; - from.SendMessage("Usage:"); - } - - SendCtor(type, ctor, from); + continue; } if (!foundCtor) { - from.SendMessage("That type is not marked constructible."); + foundCtor = true; + from.SendMessage("Usage:"); } + + SendCtor(ctor, from); } - public static void SendCtor(Type type, ConstructorInfo ctor, Mobile from) + if (!foundCtor) { - var paramList = ctor.GetParameters(); + from.SendMessage("That type is not marked constructible."); + } + } - var sb = new StringBuilder(); + public static void SendUsage(ConstructorInfo ctor, Mobile from) + { + if (!IsConstructible(ctor, from.AccessLevel)) + { + from.SendMessage("That type is not marked constructible."); + return; + } - sb.Append(type.Name); + from.SendMessage("Usage:"); + SendCtor(ctor, from); + } - for (var i = 0; i < paramList.Length; ++i) + public static void SendCtor(ConstructorInfo ctor, Mobile from) + { + var paramList = ctor.GetParameters(); + + using var sb = ValueStringBuilder.Create(); + + sb.Append(ctor.DeclaringType!.Name); + + for (var i = 0; i < paramList.Length; ++i) + { + if (i != 0) { - if (i != 0) + sb.Append(','); + } + + sb.Append(' '); + + sb.Append(paramList[i].ParameterType.Name); + sb.Append(' '); + sb.Append(paramList[i].Name); + } + + from.SendMessage(sb.ToString()); + } + + private static void TileBox_Callback(Mobile from, Point3D start, Point3D end, TileState ts) + { + var mapAvg = false; + + switch (ts.m_ZType) + { + case TileZType.Fixed: { - sb.Append(','); + start.Z = end.Z = ts.m_FixedZ; + break; + } + case TileZType.MapAverage: + { + mapAvg = true; + break; } - - sb.Append(' '); - - sb.Append(paramList[i].ParameterType.Name); - sb.Append(' '); - sb.Append(paramList[i].Name); - } - - from.SendMessage(sb.ToString()); } - private static void TileBox_Callback(Mobile from, Point3D start, Point3D end, TileState ts) + Invoke(from, start, end, ts.m_Args, null, ts.m_Outline, mapAvg); + } + + private static void Internal_OnCommand(CommandEventArgs e, bool outline) + { + var from = e.Mobile; + + if (e.Length >= 1) { - var mapAvg = false; - - switch (ts.m_ZType) - { - case TileZType.Fixed: - { - start.Z = end.Z = ts.m_FixedZ; - break; - } - case TileZType.MapAverage: - { - mapAvg = true; - break; - } - } - - Invoke(from, start, end, ts.m_Args, null, ts.m_Outline, mapAvg); + BoundingBoxPicker.Begin( + from, + (_, start, end) => + TileBox_Callback(from, start, end, new TileState(TileZType.Start, 0, e.Arguments, outline)) + ); } - - private static void Internal_OnCommand(CommandEventArgs e, bool outline) + else { - var from = e.Mobile; - - if (e.Length >= 1) + if (outline) { - BoundingBoxPicker.Begin( - from, - (map, start, end) => - TileBox_Callback(from, start, end, new TileState(TileZType.Start, 0, e.Arguments, outline)) - ); + from.SendMessage("Format: Outline [params] [set { ...}]"); } else { - if (outline) - { - from.SendMessage($"Format: Outline [params] [set {{ ...}}]"); - } - else - { - from.SendMessage($"Format: Tile [params] [set {{ ...}}]"); - } - } - } - - private static void InternalRXYZ_OnCommand(CommandEventArgs e, bool outline) - { - if (e.Length >= 6) - { - var p = new Point3D(e.Mobile.X + e.GetInt32(0), e.Mobile.Y + e.GetInt32(1), e.Mobile.Z + e.GetInt32(4)); - var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, p.Z); - - var subArgs = new string[e.Length - 5]; - - for (var i = 0; i < subArgs.Length; ++i) - { - subArgs[i] = e.Arguments[i + 5]; - } - - Invoke(e.Mobile, p, p2, subArgs, null, outline); - } - else - { - if (outline) - { - e.Mobile.SendMessage($"Format: OutlineRXYZ [params] [set {{ ...}}]"); - } - else - { - e.Mobile.SendMessage($"Format: TileRXYZ [params] [set {{ ...}}]"); - } - } - } - - private static void InternalXYZ_OnCommand(CommandEventArgs e, bool outline) - { - if (e.Length >= 6) - { - var p = new Point3D(e.GetInt32(0), e.GetInt32(1), e.GetInt32(4)); - var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, e.GetInt32(4)); - - var subArgs = new string[e.Length - 5]; - - for (var i = 0; i < subArgs.Length; ++i) - { - subArgs[i] = e.Arguments[i + 5]; - } - - Invoke(e.Mobile, p, p2, subArgs, null, outline); - } - else - { - if (outline) - { - e.Mobile.SendMessage($"Format: OutlineXYZ [params] [set {{ ...}}]" ); - } - else - { - e.Mobile.SendMessage($"Format: TileXYZ [params] [set {{ ...}}]"); - } - } - } - - private static void InternalZ_OnCommand(CommandEventArgs e, bool outline) - { - var from = e.Mobile; - - if (e.Length >= 2) - { - var subArgs = new string[e.Length - 1]; - - for (var i = 0; i < subArgs.Length; ++i) - { - subArgs[i] = e.Arguments[i + 1]; - } - - BoundingBoxPicker.Begin( - from, - (map, start, end) => - TileBox_Callback(from, start, end, new TileState(TileZType.Fixed, e.GetInt32(0), subArgs, outline)) - ); - } - else - { - if (outline) - { - from.SendMessage($"Format: OutlineZ [params] [set {{ ...}}]"); - } - else - { - from.SendMessage($"Format: TileZ [params] [set {{ ...}}]"); - } - } - } - - private static void InternalAvg_OnCommand(CommandEventArgs e, bool outline) - { - var from = e.Mobile; - - if (e.Length >= 1) - { - BoundingBoxPicker.Begin( - from, - (map, start, end) => - TileBox_Callback(from, start, end, new TileState(TileZType.MapAverage, 0, e.Arguments, outline)) - ); - } - else - { - if (outline) - { - from.SendMessage($"Format: OutlineAvg [params] [set {{ ...}}]"); - } - else - { - from.SendMessage($"Format: TileAvg [params] [set {{ ...}}]"); - } - } - } - - [Usage("Tile [params] [set { ...}]"), Description( - "Tiles an item or npc by name into a targeted bounding box. Optional constructor parameters. Optional set property list." - )] - public static void Tile_OnCommand(CommandEventArgs e) - { - Internal_OnCommand(e, false); - } - - [Usage("TileRXYZ [params] [set { ...}]"), Description( - "Tiles an item or npc by name into a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list." - )] - public static void TileRXYZ_OnCommand(CommandEventArgs e) - { - InternalRXYZ_OnCommand(e, false); - } - - [Usage("TileXYZ [params] [set { ...}]"), Description( - "Tiles an item or npc by name into a given bounding box. Optional constructor parameters. Optional set property list." - )] - public static void TileXYZ_OnCommand(CommandEventArgs e) - { - InternalXYZ_OnCommand(e, false); - } - - [Usage("TileZ [params] [set { ...}]"), Description( - "Tiles an item or npc by name into a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list." - )] - public static void TileZ_OnCommand(CommandEventArgs e) - { - InternalZ_OnCommand(e, false); - } - - [Usage("TileAvg [params] [set { ...}]"), Description( - "Tiles an item or npc by name into a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list." - )] - public static void TileAvg_OnCommand(CommandEventArgs e) - { - InternalAvg_OnCommand(e, false); - } - - [Usage("Outline [params] [set { ...}]"), Description( - "Tiles an item or npc by name around a targeted bounding box. Optional constructor parameters. Optional set property list." - )] - public static void Outline_OnCommand(CommandEventArgs e) - { - Internal_OnCommand(e, true); - } - - [Usage("OutlineRXYZ [params] [set { ...}]"), Description( - "Tiles an item or npc by name around a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list." - )] - public static void OutlineRXYZ_OnCommand(CommandEventArgs e) - { - InternalRXYZ_OnCommand(e, true); - } - - [Usage("OutlineXYZ [params] [set { ...}]"), Description( - "Tiles an item or npc by name around a given bounding box. Optional constructor parameters. Optional set property list." - )] - public static void OutlineXYZ_OnCommand(CommandEventArgs e) - { - InternalXYZ_OnCommand(e, true); - } - - [Usage("OutlineZ [params] [set { ...}]"), Description( - "Tiles an item or npc by name around a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list." - )] - public static void OutlineZ_OnCommand(CommandEventArgs e) - { - InternalZ_OnCommand(e, true); - } - - [Usage("OutlineAvg [params] [set { ...}]"), Description( - "Tiles an item or npc by name around a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list." - )] - public static void OutlineAvg_OnCommand(CommandEventArgs e) - { - InternalAvg_OnCommand(e, true); - } - - private enum TileZType - { - Start, - Fixed, - MapAverage - } - - private class TileState - { - public readonly string[] m_Args; - public readonly int m_FixedZ; - public readonly bool m_Outline; - public readonly TileZType m_ZType; - - public TileState(TileZType zType, int fixedZ, string[] args, bool outline) - { - m_ZType = zType; - m_FixedZ = fixedZ; - m_Args = args; - m_Outline = outline; + from.SendMessage("Format: Tile [params] [set { ...}]"); } } } + + private static void InternalRXYZ_OnCommand(CommandEventArgs e, bool outline) + { + if (e.Length >= 6) + { + var p = new Point3D(e.Mobile.X + e.GetInt32(0), e.Mobile.Y + e.GetInt32(1), e.Mobile.Z + e.GetInt32(4)); + var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, p.Z); + + var subArgs = new string[e.Length - 5]; + + for (var i = 0; i < subArgs.Length; ++i) + { + subArgs[i] = e.Arguments[i + 5]; + } + + Invoke(e.Mobile, p, p2, subArgs, null, outline); + } + else + { + if (outline) + { + e.Mobile.SendMessage("Format: OutlineRXYZ [params] [set { ...}]"); + } + else + { + e.Mobile.SendMessage("Format: TileRXYZ [params] [set { ...}]"); + } + } + } + + private static void InternalXYZ_OnCommand(CommandEventArgs e, bool outline) + { + if (e.Length >= 6) + { + var p = new Point3D(e.GetInt32(0), e.GetInt32(1), e.GetInt32(4)); + var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, e.GetInt32(4)); + + var subArgs = new string[e.Length - 5]; + + for (var i = 0; i < subArgs.Length; ++i) + { + subArgs[i] = e.Arguments[i + 5]; + } + + Invoke(e.Mobile, p, p2, subArgs, null, outline); + } + else + { + if (outline) + { + e.Mobile.SendMessage("Format: OutlineXYZ [params] [set { ...}]" ); + } + else + { + e.Mobile.SendMessage("Format: TileXYZ [params] [set { ...}]"); + } + } + } + + private static void InternalZ_OnCommand(CommandEventArgs e, bool outline) + { + var from = e.Mobile; + + if (e.Length >= 2) + { + var subArgs = new string[e.Length - 1]; + + for (var i = 0; i < subArgs.Length; ++i) + { + subArgs[i] = e.Arguments[i + 1]; + } + + BoundingBoxPicker.Begin( + from, + (_, start, end) => + TileBox_Callback(from, start, end, new TileState(TileZType.Fixed, e.GetInt32(0), subArgs, outline)) + ); + } + else + { + if (outline) + { + from.SendMessage("Format: OutlineZ [params] [set { ...}]"); + } + else + { + from.SendMessage("Format: TileZ [params] [set { ...}]"); + } + } + } + + private static void InternalAvg_OnCommand(CommandEventArgs e, bool outline) + { + var from = e.Mobile; + + if (e.Length >= 1) + { + BoundingBoxPicker.Begin( + from, + (_, start, end) => + TileBox_Callback(from, start, end, new TileState(TileZType.MapAverage, 0, e.Arguments, outline)) + ); + } + else + { + if (outline) + { + from.SendMessage("Format: OutlineAvg [params] [set { ...}]"); + } + else + { + from.SendMessage("Format: TileAvg [params] [set { ...}]"); + } + } + } + + [Usage("Tile [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name into a targeted bounding box. Optional constructor parameters. Optional set property list." + )] + public static void Tile_OnCommand(CommandEventArgs e) + { + Internal_OnCommand(e, false); + } + + [Usage("TileRXYZ [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name into a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list." + )] + public static void TileRXYZ_OnCommand(CommandEventArgs e) + { + InternalRXYZ_OnCommand(e, false); + } + + [Usage("TileXYZ [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name into a given bounding box. Optional constructor parameters. Optional set property list." + )] + public static void TileXYZ_OnCommand(CommandEventArgs e) + { + InternalXYZ_OnCommand(e, false); + } + + [Usage("TileZ [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name into a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list." + )] + public static void TileZ_OnCommand(CommandEventArgs e) + { + InternalZ_OnCommand(e, false); + } + + [Usage("TileAvg [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name into a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list." + )] + public static void TileAvg_OnCommand(CommandEventArgs e) + { + InternalAvg_OnCommand(e, false); + } + + [Usage("Outline [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name around a targeted bounding box. Optional constructor parameters. Optional set property list." + )] + public static void Outline_OnCommand(CommandEventArgs e) + { + Internal_OnCommand(e, true); + } + + [Usage("OutlineRXYZ [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name around a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list." + )] + public static void OutlineRXYZ_OnCommand(CommandEventArgs e) + { + InternalRXYZ_OnCommand(e, true); + } + + [Usage("OutlineXYZ [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name around a given bounding box. Optional constructor parameters. Optional set property list." + )] + public static void OutlineXYZ_OnCommand(CommandEventArgs e) + { + InternalXYZ_OnCommand(e, true); + } + + [Usage("OutlineZ [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name around a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list." + )] + public static void OutlineZ_OnCommand(CommandEventArgs e) + { + InternalZ_OnCommand(e, true); + } + + [Usage("OutlineAvg [params] [set { ...}]")] + [Description( + "Tiles an item or npc by name around a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list." + )] + public static void OutlineAvg_OnCommand(CommandEventArgs e) + { + InternalAvg_OnCommand(e, true); + } + + private enum TileZType + { + Start, + Fixed, + MapAverage + } + + private class TileState + { + public readonly string[] m_Args; + public readonly int m_FixedZ; + public readonly bool m_Outline; + public readonly TileZType m_ZType; + + public TileState(TileZType zType, int fixedZ, string[] args, bool outline) + { + m_ZType = zType; + m_FixedZ = fixedZ; + m_Args = args; + m_Outline = outline; + } + } } diff --git a/Projects/UOContent/Commands/Object Creation/AddGump.cs b/Projects/UOContent/Commands/Object Creation/AddGump.cs index 22a832f22..fc38b9ad1 100644 --- a/Projects/UOContent/Commands/Object Creation/AddGump.cs +++ b/Projects/UOContent/Commands/Object Creation/AddGump.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Server.Network; using Server.Targeting; @@ -10,13 +11,13 @@ public class AddGump : DynamicGump private static readonly Type _typeofItem = typeof(Item); private static readonly Type _typeofMobile = typeof(Mobile); private readonly int _page; - private readonly Type[] _searchResults; + private readonly ConstructorInfo[] _searchResults; private readonly string _searchString; private readonly bool _explicitSearch; public override bool Singleton => true; - public AddGump(string searchString, int page, Type[] searchResults, bool explicitSearch) : base(50, 50) + public AddGump(string searchString, int page, ConstructorInfo[] searchResults, bool explicitSearch) : base(50, 50) { _searchString = searchString; _searchResults = searchResults; @@ -50,7 +51,7 @@ public class AddGump : DynamicGump { var index = i % 10; - builder.AddLabel(44, 39 + index * 20, 0x480, _searchResults[i].Name); + builder.AddLabel(44, 39 + index * 20, 0x480, _searchResults[i].DeclaringType!.Name); builder.AddButton(10, 39 + index * 20, 4023, 4025, 4 + i); } } @@ -102,42 +103,83 @@ public class AddGump : DynamicGump private static void AddMenu_OnCommand(CommandEventArgs e) { var val = e.ArgString.Trim(); - Type[] types; + ConstructorInfo[] ctors; var explicitSearch = false; if (val.Length == 0) { - types = Type.EmptyTypes; + ctors = []; } else if (val.Length < 3) { e.Mobile.SendMessage("Invalid search string."); - types = Type.EmptyTypes; + ctors = []; } else { - types = Match(val); + ctors = MatchEmptyCtor(val); explicitSearch = true; } - e.Mobile.SendGump(new AddGump(val, 0, types, explicitSearch)); + e.Mobile.SendGump(new AddGump(val, 0, ctors, explicitSearch)); } - private static void Match(string match, Type[] types, HashSet results) + private static bool ExactMatch(string match, Assembly assembly, out Type type) { - if (match.Length == 0) + if (!_mobileItemTypes.TryGetValue(assembly, out var mobileItemTypes)) { - return; + List typeList = []; + var types = AssemblyHandler.GetTypeCache(assembly).Types; + for (var i = 0; i < types.Length; i++) + { + var t = types[i]; + if (_typeofMobile.IsAssignableFrom(t) || _typeofItem.IsAssignableFrom(t)) + { + typeList.Add(t); + } + } + + _mobileItemTypes[assembly] = mobileItemTypes = typeList.ToArray(); } - match = match.ToLower(); - - for (var i = 0; i < types.Length; i++) + for (var i = 0; i < mobileItemTypes.Length; i++) { - var t = types[i]; + var t = mobileItemTypes[i]; - if (!(_typeofMobile.IsAssignableFrom(t) || _typeofItem.IsAssignableFrom(t)) || - !t.Name.InsensitiveContains(match) || results.Contains(t)) + if (t.Name.InsensitiveEquals(match)) + { + type = t; + return true; + } + } + + type = null; + return false; + } + + private static void MatchEmptyCtor(string match, Assembly assembly, List results) + { + if (!_mobileItemTypes.TryGetValue(assembly, out var mobileItemTypes)) + { + List typeList = []; + var types = AssemblyHandler.GetTypeCache(assembly).Types; + for (var i = 0; i < types.Length; i++) + { + var t = types[i]; + if (_typeofMobile.IsAssignableFrom(t) || _typeofItem.IsAssignableFrom(t)) + { + typeList.Add(t); + } + } + + _mobileItemTypes[assembly] = mobileItemTypes = typeList.ToArray(); + } + + for (var i = 0; i < mobileItemTypes.Length; i++) + { + var t = mobileItemTypes[i]; + + if (!t.Name.InsensitiveContains(match)) { continue; } @@ -158,47 +200,70 @@ public class AddGump : DynamicGump } } - if (isEmptyCtor && ctors[j].IsDefined(typeof(ConstructibleAttribute), false)) + if (isEmptyCtor && ctor.GetCustomAttributes(Types.OfConstructible, false).Length > 0) { - results.Add(t); + results.Add(ctor); break; } } } } - public static Type[] Match(string match) + private static readonly Dictionary _mobileItemTypes = []; + + public static ConstructorInfo[] MatchEmptyCtor(string match) { - var results = new HashSet(); - Type[] types; - - var asms = AssemblyHandler.Assemblies; - - for (var i = 0; i < asms.Length; ++i) + if (string.IsNullOrWhiteSpace(match)) { - types = AssemblyHandler.GetTypeCache(asms[i]).Types; - Match(match, types, results); + return []; } - types = AssemblyHandler.GetTypeCache(Core.Assembly).Types; - Match(match, types, results); + match = match.ToLower(); + + List results = []; + MatchEmptyCtor(match, Core.Assembly, results); + + for (var i = 0; i < AssemblyHandler.Assemblies.Length; ++i) + { + MatchEmptyCtor(match, AssemblyHandler.Assemblies[i], results); + } if (results.Count == 0) { - return Array.Empty(); + return []; } - var finalResults = new Type[results.Count]; - var index = 0; - foreach (var t in results) - { - finalResults[index++] = t; - } + var finalResults = results.ToArray(); + Array.Sort(finalResults, ConstructorNameComparer.Instance); - Array.Sort(finalResults, TypeNameComparer.Instance); return finalResults; } + public static Type ExactMatch(string match) + { + if (string.IsNullOrWhiteSpace(match)) + { + return null; + } + + match = match.ToLower(); + + if (ExactMatch(match, Core.Assembly, out var type)) + { + return type; + } + + for (var i = 0; i < AssemblyHandler.Assemblies.Length; ++i) + { + if (ExactMatch(match, AssemblyHandler.Assemblies[i], out type)) + { + return type; + } + } + + return null; + } + public override void OnResponse(NetState sender, in RelayInfo info) { var from = sender.Mobile; @@ -216,7 +281,7 @@ public class AddGump : DynamicGump } else { - from.SendGump(new AddGump(match, 0, Match(match), true)); + from.SendGump(new AddGump(match, 0, MatchEmptyCtor(match), true)); } break; @@ -259,29 +324,30 @@ public class AddGump : DynamicGump } } - private class TypeNameComparer : IComparer + private class ConstructorNameComparer : IComparer { - public static readonly TypeNameComparer Instance = new(); - public int Compare(Type x, Type y) => string.CompareOrdinal(x?.Name, y?.Name); + public static readonly ConstructorNameComparer Instance = new(); + public int Compare(ConstructorInfo x, ConstructorInfo y) => + x?.DeclaringType!.Name.CompareOrdinal(y?.DeclaringType!.Name) ?? 0; } public class InternalTarget : Target { - private readonly int m_Page; - private readonly Type[] m_SearchResults; - private readonly string m_SearchString; - private readonly Type m_Type; + private readonly int _page; + private readonly ConstructorInfo[] _searchResults; + private readonly string _searchString; + private readonly ConstructorInfo _ctor; - public InternalTarget(Type type, Type[] searchResults, string searchString, int page) : base( + public InternalTarget(ConstructorInfo ctor, ConstructorInfo[] searchResults, string searchString, int page) : base( -1, true, TargetFlags.None ) { - m_Type = type; - m_SearchResults = searchResults; - m_SearchString = searchString; - m_Page = page; + _ctor = ctor; + _searchResults = searchResults; + _searchString = searchString; + _page = page; } protected override void OnTarget(Mobile from, object o) @@ -298,16 +364,15 @@ public class AddGump : DynamicGump _ => new Point3D(ip) }; - Commands.Add.Invoke(from, new Point3D(p), new Point3D(p), new[] { m_Type.Name }); - - from.Target = new InternalTarget(m_Type, m_SearchResults, m_SearchString, m_Page); + Commands.Add.Invoke(from, new Point3D(p), new Point3D(p), _ctor); + from.Target = new InternalTarget(_ctor, _searchResults, _searchString, _page); } protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType) { if (cancelType == TargetCancelType.Canceled) { - from.SendGump(new AddGump(m_SearchString, m_Page, m_SearchResults, true)); + from.SendGump(new AddGump(_searchString, _page, _searchResults, true)); } } } diff --git a/Projects/UOContent/Engines/ConPVP/DuelContext.cs b/Projects/UOContent/Engines/ConPVP/DuelContext.cs index a01e24058..ef7fb5b89 100644 --- a/Projects/UOContent/Engines/ConPVP/DuelContext.cs +++ b/Projects/UOContent/Engines/ConPVP/DuelContext.cs @@ -1764,7 +1764,7 @@ namespace Server.Engines.ConPVP { if (Rematch) { - mob.SendMessage(0x22, $"You have rejected the rematch."); + mob.SendMessage(0x22, "You have rejected the rematch."); } else { diff --git a/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs b/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs index cfe129675..7ad4aa937 100644 --- a/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs +++ b/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs @@ -263,11 +263,11 @@ namespace Server.Engines.MLQuests if (enable) { - m.SendMessage($"Serialization for all quests is now enabled."); + m.SendMessage("Serialization for all quests is now enabled."); } else { - m.SendMessage($"Serialization for all quests is now disabled."); + m.SendMessage("Serialization for all quests is now disabled."); } if (AutoGenerateNew && !enable) diff --git a/Projects/UOContent/Special Systems/Items/Stones/GamblingStone.cs b/Projects/UOContent/Special Systems/Items/Stones/GamblingStone.cs index dc3898e39..c949b0cbc 100644 --- a/Projects/UOContent/Special Systems/Items/Stones/GamblingStone.cs +++ b/Projects/UOContent/Special Systems/Items/Stones/GamblingStone.cs @@ -82,7 +82,7 @@ public partial class GamblingStone : Item } else { - from.SendMessage(0x22, $"You need at least 250gp in your backpack to use this."); + from.SendMessage(0x22, "You need at least 250gp in your backpack to use this."); } } } diff --git a/Projects/UOContent/World Saves/SaveCommands.cs b/Projects/UOContent/World Saves/SaveCommands.cs index 21da36a1f..29882bc4e 100644 --- a/Projects/UOContent/World Saves/SaveCommands.cs +++ b/Projects/UOContent/World Saves/SaveCommands.cs @@ -35,11 +35,11 @@ namespace Server.Commands if (enabled) { - e.Mobile.SendMessage($"Saves have been enabled."); + e.Mobile.SendMessage("Saves have been enabled."); } else { - e.Mobile.SendMessage($"Saves have been disabled."); + e.Mobile.SendMessage("Saves have been disabled."); } } else