using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Net.Sockets; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Xml; using Server.Buffers; using Server.Collections; using Server.Random; using Server.Text; namespace Server; public static partial class Utility { private static Dictionary _ipAddressTable; private static readonly Stack m_ConsoleColors = new(); public static void Separate(StringBuilder sb, string value, string separator) { if (sb.Length > 0) { sb.Append(separator); } sb.Append(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string Intern(this string str) => str?.Length > 0 ? string.Intern(str) : str; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Intern(ref string str) { str = Intern(str); } public static IPAddress Intern(IPAddress ipAddress) { if (ipAddress == null) { return null; } if (ipAddress.IsIPv4MappedToIPv6) { ipAddress = ipAddress.MapToIPv4(); } _ipAddressTable ??= new Dictionary(); if (!_ipAddressTable.TryGetValue(ipAddress, out var interned)) { interned = ipAddress; _ipAddressTable[ipAddress] = interned; } return interned; } public static void Intern(ref IPAddress ipAddress) { ipAddress = Intern(ipAddress); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ApplyCidrMask(ref ulong high, ref ulong low, int prefixLength, bool isMax) { // This should never happen, a 0 CIDR is not valid if (prefixLength == 0) { high = low = ~0UL; return; } if (prefixLength == 128) { return; } if (prefixLength == 64) { low = 0; return; } if (prefixLength < 64) { int bitsToFlip = 64 - prefixLength; ulong highMask = isMax ? ~0UL >> bitsToFlip : ~0UL << (bitsToFlip + 1); high = isMax ? high | highMask : high & highMask; low = isMax ? ~0UL : 0UL; } else { int bitsToFlip = 128 - prefixLength; ulong lowMask = isMax ? ~0UL >> (64 - bitsToFlip) : ~0UL << bitsToFlip; low = isMax ? low | lowMask : low & lowMask; } } // Converts an IPAddress to a UInt128 in IPv6 format public static UInt128 ToUInt128(this IPAddress ip) { if (ip.AddressFamily == AddressFamily.InterNetwork && !ip.IsIPv4MappedToIPv6) { Span integer = stackalloc byte[4]; return !ip.TryWriteBytes(integer, out _) ? (UInt128)0 : new UInt128(0, 0xFFFF00000000UL | BinaryPrimitives.ReadUInt32BigEndian(integer)); } Span bytes = stackalloc byte[16]; if (!ip.TryWriteBytes(bytes, out _)) { return 0; } ulong high = BinaryPrimitives.ReadUInt64BigEndian(bytes[..8]); ulong low = BinaryPrimitives.ReadUInt64BigEndian(bytes.Slice(8, 8)); return new UInt128(high, low); } // Converts a UInt128 in IPv6 format to an IPAddress public static IPAddress ToIpAddress(this UInt128 value, bool mapToIpv6 = false) { // IPv4 mapped IPv6 address if (!mapToIpv6 && value >= 0xFFFF00000000UL && value <= 0xFFFFFFFFFFFFUL) { var newAddress = IPAddress.HostToNetworkOrder((int)value); return new IPAddress(unchecked((uint)newAddress)); } Span bytes = stackalloc byte[16]; // 128 bits for IPv6 address ((IBinaryInteger)value).WriteBigEndian(bytes); return new IPAddress(bytes); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 CreateCidrAddress(ReadOnlySpan bytes, int prefixLength, bool isMax) { ulong high = BinaryPrimitives.ReadUInt64BigEndian(bytes[..8]); ulong low = BinaryPrimitives.ReadUInt64BigEndian(bytes.Slice(8, 8)); if (prefixLength < 128) { ApplyCidrMask(ref high, ref low, prefixLength, isMax); } return new UInt128(high, low); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteMappedIPv6To(this IPAddress ipAddress, Span destination) { if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { ipAddress.TryWriteBytes(destination, out _); return; } destination[..8].Clear(); // Local init is off BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(8, 4), 0xFFFF); ipAddress.TryWriteBytes(destination.Slice(12, 4), out _); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool MatchClassC(this IPAddress ip1, IPAddress ip2) => ip1.MatchCidr(24, ip2); public static bool MatchCidr(this IPAddress cidrAddress, int prefixLength, IPAddress address) { Span cidrBytes = stackalloc byte[16]; cidrAddress.WriteMappedIPv6To(cidrBytes); if (cidrAddress.AddressFamily != AddressFamily.InterNetworkV6) { prefixLength += 96; // 32 -> 128 } var min = CreateCidrAddress(cidrBytes, prefixLength, false); var max = CreateCidrAddress(cidrBytes, prefixLength, true); var ip = address.ToUInt128(); return ip >= min && ip <= max; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string FixHtml(this string str) => ((ReadOnlySpan)str).FixHtml(); public static string FixHtml(this ReadOnlySpan str) { if (str.IsNullOrWhiteSpace()) { return str.ToString(); } var chars = STArrayPool.Shared.Rent(str.Length); var span = chars.AsSpan(0, str.Length); str.CopyTo(span); FixHtml(span); var fixedStr = span.ToString(); STArrayPool.Shared.Return(chars); return fixedStr; } public static void FixHtml(this Span chars) { if (chars.Length == 0) { return; } ReadOnlySpan invalid = ['<', '>', '#']; ReadOnlySpan replacement = ['(', ')', '-']; chars.ReplaceAny(invalid, replacement); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PooledArraySpanFormattable FixHtmlFormattable(this string str) => ((ReadOnlySpan)str).FixHtmlFormattable(); public static PooledArraySpanFormattable FixHtmlFormattable(this ReadOnlySpan str) { var chars = STArrayPool.Shared.Rent(str.Length); var span = chars.AsSpan(0, str.Length); str.CopyTo(span); var formattable = new PooledArraySpanFormattable(chars, str.Length); if (!str.IsNullOrWhiteSpace()) { FixHtml(span); } return formattable; } public static Direction GetDirection(Point3D from, Point3D to) => GetDirection(from.X, from.Y, to.X, to.Y); public static Direction GetDirection(Point2D from, Point2D to) => GetDirection(from.X, from.Y, to.X, to.Y); public static Direction GetDirection(int fromX, int fromY, int toX, int toY) { var dx = toX - fromX; var dy = toY - fromY; var adx = Abs(dx); var ady = Abs(dy); if (adx >= ady * 3) { return dx > 0 ? Direction.East : Direction.West; } if (ady >= adx * 3) { return dy > 0 ? Direction.South : Direction.North; } if (dx > 0) { return dy > 0 ? Direction.Down : Direction.Right; } return dy > 0 ? Direction.Left : Direction.Up; } public static object GetArrayCap(Array array, int index, object emptyValue = null) => array.Length > 0 ? array.GetValue(Math.Clamp(index, 0, array.Length - 1)) : emptyValue; public static void FixPoints(ref Point3D top, ref Point3D bottom) { if (bottom.m_X < top.m_X) { (top.m_X, bottom.m_X) = (bottom.m_X, top.m_X); } if (bottom.m_Y < top.m_Y) { (top.m_Y, bottom.m_Y) = (bottom.m_Y, top.m_Y); } if (bottom.m_Z < top.m_Z) { (top.m_Z, bottom.m_Z) = (bottom.m_Z, top.m_Z); } } public static void FormatBuffer(this TextWriter op, ReadOnlySpan data) { op.WriteLine(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); op.WriteLine(" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); var totalLength = data.Length; if (totalLength <= 0) { op.WriteLine("0000 "); return; } Span lineBytes = stackalloc byte[16]; Span lineChars = stackalloc char[47]; for (var i = 0; i < totalLength; i += 16) { var length = Math.Min(data.Length - i, 16); data.Slice(i, length).CopyTo(lineBytes); var charsWritten = ((ReadOnlySpan)lineBytes[..length]).ToSpacedHexString(lineChars); op.Write("{0:X4} ", i); op.Write(lineChars[..charsWritten]); op.WriteLine(); } } public static void PushColor(ConsoleColor color) { try { m_ConsoleColors.Push(Console.ForegroundColor); Console.ForegroundColor = color; } catch { // ignored } } public static void PopColor() { try { Console.ForegroundColor = m_ConsoleColors.Pop(); } catch { // ignored } } public static bool NumberBetween(double num, int bound1, int bound2, double allowance) { if (bound1 > bound2) { (bound1, bound2) = (bound2, bound1); } return num < bound2 + allowance && num > bound1 - allowance; } public static void AssignRandomHair(Mobile m, int hue) { m.HairItemID = m.Race.RandomHair(m); m.HairHue = hue; } public static void AssignRandomHair(Mobile m, bool randomHue = true) { m.HairItemID = m.Race.RandomHair(m); if (randomHue) { m.HairHue = m.Race.RandomHairHue(); } } public static void AssignRandomFacialHair(Mobile m, int hue) { m.FacialHairItemID = m.Race.RandomFacialHair(m); m.FacialHairHue = hue; } public static void AssignRandomFacialHair(Mobile m, bool randomHue = true) { m.FacialHairItemID = m.Race.RandomFacialHair(m); if (randomHue) { m.FacialHairHue = m.Race.RandomHairHue(); } } // Using this instead of Linq Cast<> means we can ditch the yield and enforce contravariance public static HashSet SafeConvertSet(this IEnumerable coll) where TOutput : TInput => coll.SafeConvert, TInput, TOutput>(); public static List SafeConvertList(this IEnumerable coll) where TOutput : TInput => coll.SafeConvert, TInput, TOutput>(); public static TColl SafeConvert(this IEnumerable coll) where TOutput : TInput where TColl : ICollection, new() { var outputList = new TColl(); foreach (var entry in coll) { if (entry is TOutput outEntry) { outputList.Add(outEntry); } } return outputList; } // ToArray returns an array containing the contents of the List. // This requires copying the List, which is an O(n) operation. public static List ToList(this PooledRefList poolList) where T : R { var size = poolList.Count; var items = poolList._items; var list = new List(size); if (size == 0) { return list; } for (var i = 0; i < size; i++) { list.Add(items[i]); } return list; } public static bool ToBoolean(string value) => bool.TryParse(value, out var b) ? b : value.InsensitiveEquals("enabled") || value.InsensitiveEquals("on"); public static double ToDouble(string value) { #pragma warning disable CA1806 // Do not ignore method results double.TryParse(value, out var d); #pragma warning restore CA1806 // Do not ignore method results return d; } public static TimeSpan ToTimeSpan(string value) { #pragma warning disable CA1806 // Do not ignore method results TimeSpan.TryParse(value, out var t); #pragma warning restore CA1806 // Do not ignore method results return t; } public static int ToInt32(ReadOnlySpan value) { int i; #pragma warning disable CA1806 // Do not ignore method results if (value.StartsWithOrdinal("0x")) { int.TryParse(value[2..], NumberStyles.HexNumber, null, out i); } else { int.TryParse(value, out i); } #pragma warning restore CA1806 // Do not ignore method results return i; } public static uint ToUInt32(ReadOnlySpan value) { uint i; #pragma warning disable CA1806 // Do not ignore method results if (value.InsensitiveStartsWith("0x")) { uint.TryParse(value[2..], NumberStyles.HexNumber, null, out i); } else { uint.TryParse(value, out i); } #pragma warning restore CA1806 // Do not ignore method results return i; } public static bool ToInt32(ReadOnlySpan value, out int i) => value.InsensitiveStartsWith("0x") ? int.TryParse(value[2..], NumberStyles.HexNumber, null, out i) : int.TryParse(value, out i); public static bool ToUInt32(ReadOnlySpan value, out uint i) => value.InsensitiveStartsWith("0x") ? uint.TryParse(value[2..], NumberStyles.HexNumber, null, out i) : uint.TryParse(value, out i); public static int GetXMLInt32(string intString, int defaultValue) { try { return XmlConvert.ToInt32(intString); } catch { return int.TryParse(intString, out var val) ? val : defaultValue; } } public static uint GetXMLUInt32(string uintString, uint defaultValue) { try { return XmlConvert.ToUInt32(uintString); } catch { return uint.TryParse(uintString, out var val) ? val : defaultValue; } } public static DateTime GetXMLDateTime(string dateTimeString, DateTime defaultValue) { try { return XmlConvert.ToDateTime(dateTimeString, XmlDateTimeSerializationMode.Utc); } catch { return DateTime.TryParse(dateTimeString, out var d) ? d : defaultValue; } } public static TimeSpan GetXMLTimeSpan(string timeSpanString, TimeSpan defaultValue) { try { return XmlConvert.ToTimeSpan(timeSpanString); } catch { return defaultValue; } } public static string GetAttribute(XmlElement node, string attributeName, string defaultValue = null) => node?.Attributes[attributeName]?.Value ?? defaultValue; public static string GetText(XmlElement node, string defaultValue) => node?.InnerText ?? defaultValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InRange(int p1X, int p1Y, int p2X, int p2Y, int range) => p1X >= p2X - range && p1X <= p2X + range && p1Y >= p2Y - range && p1Y <= p2Y + range; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InRange(Point2D p1, Point2D p2, int range) => InRange(p1.m_X, p1.m_Y, p2.m_X, p2.m_Y, range); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InUpdateRange(Point2D p1, Point2D p2) => InRange(p1, p2, Core.GlobalUpdateRange); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InRange(Point3D p1, Point3D p2, int range) => InRange(p1.m_X, p1.m_Y, p2.m_X, p2.m_Y, range); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InUpdateRange(Point3D p1, Point3D p2) => InRange(p1, p2, Core.GlobalUpdateRange); // Optimized method for handling 50% random chances in succession up to a maximum public static int CoinFlips(int amount, int maximum) { var heads = 0; while (amount > 0) { // Range is 2^amount exclusively, maximum of 62 bits can be used ulong num = amount >= 62 ? (ulong)BuiltInRng.NextLong() : (ulong)BuiltInRng.Next(1L << amount); heads += BitOperations.PopCount(num); if (heads >= maximum) { return maximum; } // 64 bits minus sign bit and exclusive maximum leaves 62 bits amount -= 62; } return heads; } public static int CoinFlips(int amount) { var heads = 0; while (amount > 0) { // Range is 2^amount exclusively, maximum of 62 bits can be used ulong num = amount >= 62 ? (ulong)BuiltInRng.NextLong() : (ulong)BuiltInRng.Next(1L << amount); heads += BitOperations.PopCount(num); // 64 bits minus sign bit and exclusive maximum leaves 62 bits amount -= 62; } return heads; } public static int Dice(int amount, int sides, int bonus) { if (amount <= 0 || sides <= 0) { return 0; } int total; if (sides == 2) { total = CoinFlips(amount); } else { total = 0; for (var i = 0; i < amount; ++i) { total += BuiltInRng.Next(1, sides); } } return total + bonus; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Shuffle(this T[] array) => BuiltInRng.Generator.Shuffle(array); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Shuffle(this List list) => BuiltInRng.Generator.Shuffle(CollectionsMarshal.AsSpan(list)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Shuffle(this Span span) => BuiltInRng.Generator.Shuffle(span); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Shuffle(this PooledRefList list) => BuiltInRng.Generator.Shuffle(list._items.AsSpan(0, list.Count)); /** * Gets a random sample from the source list. * Not meant for unbounded lists. Does not shuffle or modify source. */ public static T[] RandomSample(this T[] source, int count) { if (count <= 0) { return Array.Empty(); } var length = source.Length; Span list = stackalloc bool[length]; list.Clear(); var sampleList = new T[count]; var i = 0; do { var rand = Random(length); if (!list[rand]) { list[rand] = true; sampleList[i++] = source[rand]; } } while (i < count); return sampleList; } public static List RandomSample(this List source, int count) { if (count <= 0) { return []; } var length = source.Count; Span list = stackalloc bool[length]; list.Clear(); var sampleList = new List(count); var i = 0; do { var rand = Random(length); if (!list[rand]) { list[rand] = true; sampleList[i++] = source[rand]; } } while (i < count); return sampleList; } public static void RandomSample(this T[] source, int count, List dest) { if (count <= 0) { return; } var length = source.Length; Span list = stackalloc bool[length]; list.Clear(); var i = 0; do { var rand = Random(length); if (!list[rand]) { list[rand] = true; dest.Add(source[rand]); } } while (++i < count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomList(params T[] list) => list.RandomElement(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomElement(this ReadOnlySpan list) => list.Length == 0 ? default : list[Random(list.Length)]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomElement(this T[] list) => list.RandomElement(default); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomElement(this IList list) => list.RandomElement(default); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static WeightedValue RandomWeightedElement(this IList> weightedValues) { var totalWeight = 0; for (var i = 0; i < weightedValues.Count; i++) { totalWeight += weightedValues[i].Weight; } return RandomWeightedElement(weightedValues, totalWeight); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static WeightedValue RandomWeightedElement(this ReadOnlySpan> weightedValues, int totalWeight) { var random = Random(totalWeight); for (var i = 0; i < weightedValues.Length; i++) { var weightedValue = weightedValues[i]; random -= weightedValue.Weight; if (random <= 0) { return weightedValue; } } return default; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static WeightedValue RandomWeightedElement(this ReadOnlySpan> weightedValues) { var totalWeight = 0; for (var i = 0; i < weightedValues.Length; i++) { totalWeight += weightedValues[i].Weight; } return RandomWeightedElement(weightedValues, totalWeight); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static WeightedValue RandomWeightedElement(this IList> weightedValues, int totalWeight) { var random = Random(totalWeight); for (var i = 0; i < weightedValues.Count; i++) { var weightedValue = weightedValues[i]; random -= weightedValue.Weight; if (random <= 0) { return weightedValue; } } return default; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static WeightedValue RandomWeightedElement(this PooledRefList> weightedValues) { var totalWeight = 0; for (var i = 0; i < weightedValues.Count; i++) { totalWeight += weightedValues[i].Weight; } return RandomWeightedElement(weightedValues, totalWeight); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static WeightedValue RandomWeightedElement( this PooledRefList> weightedValues, int totalWeight ) { var random = Random(totalWeight); for (var i = 0; i < weightedValues.Count; i++) { var weightedValue = weightedValues[i]; random -= weightedValue.Weight; if (random <= 0) { return weightedValue; } } return default; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T TakeRandomElement(this IList list) { if (list.Count == 0) { return default; } var index = Random(list.Count); var value = list[index]; list.RemoveAt(index); return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomElement(this T[] list, T valueIfZero) => list.Length == 0 ? valueIfZero : list[Random(list.Length)]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomElement(this IList list, T valueIfZero) => list.Count == 0 ? valueIfZero : list[Random(list.Count)]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool RandomBool() => BuiltInRng.Next(2) == 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TimeSpan RandomMinMax(TimeSpan min, TimeSpan max) => new(RandomMinMax(min.Ticks, max.Ticks)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double RandomMinMax(double min, double max) { if (min > max) { (min, max) = (max, min); } else if (min == max) { return min; } return min + BuiltInRng.NextDouble() * (max - min); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RandomMinMax(int min, int max) { if (min > max) { (min, max) = (max, min); } else if (min == max) { return min; } return min + BuiltInRng.Next(max - min + 1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long RandomMinMax(long min, long max) { if (min > max) { (min, max) = (max, min); } else if (min == max) { return min; } return min + BuiltInRng.Next(max - min + 1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Random(int from, int count) => BuiltInRng.Next(from, count); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Random(int count) => count < 0 ? -BuiltInRng.Next(-count) : BuiltInRng.Next(count); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long Random(long from, long count) => BuiltInRng.Next(from, count); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long Random(long count) => count < 0 ? -BuiltInRng.Next(-count) : BuiltInRng.Next(count); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RandomBytes(Span buffer) => BuiltInRng.NextBytes(buffer); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double RandomDouble() => BuiltInRng.NextDouble(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Point3D RandomPointIn(Rectangle2D rect, Map map) { var x = Random(rect.X, rect.Width); var y = Random(rect.Y, rect.Height); return new Point3D(x, y, map.GetAverageZ(x, y)); } /// /// Random pink, blue, green, orange, red or yellow hue /// public static int RandomNondyedHue() { return Random(6) switch { 0 => RandomPinkHue(), 1 => RandomBlueHue(), 2 => RandomGreenHue(), 3 => RandomOrangeHue(), 4 => RandomRedHue(), 5 => RandomYellowHue(), _ => 0 }; } /// /// Random hue in the range 1201-1254 /// public static int RandomPinkHue() => Random(1201, 54); /// /// Random hue in the range 1301-1354 /// public static int RandomBlueHue() => Random(1301, 54); /// /// Random hue in the range 1401-1454 /// public static int RandomGreenHue() => Random(1401, 54); /// /// Random hue in the range 1501-1554 /// public static int RandomOrangeHue() => Random(1501, 54); /// /// Random hue in the range 1601-1654 /// public static int RandomRedHue() => Random(1601, 54); /// /// Random hue in the range 1701-1754 /// public static int RandomYellowHue() => Random(1701, 54); /// /// Random hue in the range 1801-1908 /// public static int RandomNeutralHue() => Random(1801, 108); /// /// Random hue in the range 2001-2018 /// public static int RandomSnakeHue() => Random(2001, 18); /// /// Random hue in the range 2101-2130 /// public static int RandomBirdHue() => Random(2101, 30); /// /// Random hue in the range 2201-2224 /// public static int RandomSlimeHue() => Random(2201, 24); /// /// Random hue in the range 2301-2318 /// public static int RandomAnimalHue() => Random(2301, 18); /// /// Random hue in the range 2401-2430 /// public static int RandomMetalHue() => Random(2401, 30); public static int ClipDyedHue(int hue) => hue < 2 ? 2 : hue > 1001 ? 1001 : hue; /// /// Random hue in the range 2-1001 /// public static int RandomDyedHue() => Random(2, 1000); /// /// Random hue from 0x62, 0x71, 0x03, 0x0D, 0x13, 0x1C, 0x21, 0x30, 0x37, 0x3A, 0x44, 0x59 /// public static int RandomBrightHue() => RandomDouble() < 0.1 ? RandomList(0x62, 0x71) : RandomList(0x03, 0x0D, 0x13, 0x1C, 0x21, 0x30, 0x37, 0x3A, 0x44, 0x59); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T Clamp(this T val, T min, T max) where T : IComparable => val.CompareTo(min) < 0 ? min : val.CompareTo(max) > 0 ? max : val; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T Min(T val, T min) where T : IComparable => val.CompareTo(min) < 0 ? val : min; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T Max(T val, T max) where T : IComparable => val.CompareTo(max) > 0 ? val : max; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Tidy(this List list) where T : ISerializable { for (int i = list.Count - 1; i >= 0; i--) { var entry = list[i]; if (entry?.Deleted != false) { list.RemoveAt(i); } } list.TrimExcess(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Tidy(this HashSet set) where T : ISerializable { set.RemoveWhere(entry => entry?.Deleted != false); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Tidy(this Dictionary dictionary) { var serializable = typeof(ISerializable); var serializableKey = typeof(K).IsAssignableTo(serializable); var serializableValue = typeof(V).IsAssignableTo(serializable); if (!serializableKey && !serializableValue) { return; } using var queue = PooledRefQueue.Create(); foreach (var (key, value) in dictionary) { if (serializableKey) { if (key == null || ((ISerializable)key).Deleted) { queue.Enqueue(key); } } else { if (value == null || ((ISerializable)value).Deleted) { queue.Enqueue(key); } } } while (queue.Count > 0) { dictionary.Remove(queue.Dequeue()); } dictionary.TrimExcess(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int NumberOfSetBits(this ulong i) { i -= (i >> 1) & 0x5555555555555555UL; i = (i & 0x3333333333333333UL) + ((i >> 2) & 0x3333333333333333UL); return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Abs(this int value) { int mask = value >> 31; return (value + mask) ^ mask; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetTimeStamp() => Core.Now.ToTimeStamp(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToTimeStamp(this DateTime dt) => dt.ToString("yyyy-MM-dd-HH-mm-ss"); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add(ref List list, T value) { list ??= []; list.Add(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add(ref HashSet set, T value) { set ??= []; set.Add(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add(ref Dictionary dict, K key, V value) { dict ??= new Dictionary(); dict.Add(key, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Remove(ref List list, T value) { if (list?.Remove(value) != true) { return false; } if (list.Count == 0) { list = null; } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Remove(ref HashSet set, T value) { if (set?.Remove(value) != true) { return false; } if (set.Count == 0) { set = null; } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Remove(ref Dictionary dict, K key) { if (dict?.Remove(key) != true) { return false; } if (dict.Count == 0) { dict = null; } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Remove(ref Dictionary dict, K key, out V value) { if (dict?.Remove(key, out value) != true) { value = default; return false; } if (dict.Count == 0) { dict = null; } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Replace(ref List list, T oldValue, T newValue) { if (oldValue != null && newValue != null) { var index = list?.IndexOf(oldValue) ?? -1; if (index >= 0) { list![index] = newValue; } else { Add(ref list, newValue); } } else if (oldValue != null) { Remove(ref list, oldValue); } else if (newValue != null) { Add(ref list, newValue); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Replace(ref Dictionary dict, K key, V oldValue, V newValue) { if (newValue != null) { Add(ref dict, key, newValue); } else if (oldValue != null) { Remove(ref dict, key); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clear(ref List list) { list.Clear(); list = null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clear(ref HashSet set) { set.Clear(); set = null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clear(ref Dictionary dict) { dict.Clear(); dict = null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void DateToComponents( DateTime date, out int year, out int month, out int day, out DayOfWeek dayOfWeek, out int hour, out int min, out int sec ) { year = date.Year; month = date.Month; day = date.Day; dayOfWeek = date.DayOfWeek; hour = date.Hour; min = date.Minute; sec = date.Second; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] Combine(this IList source, params IList[] arrays) => source.Combine(false, arrays); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] CombinePooled(this IList source, params IList[] arrays) => source.Combine(true, arrays); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] Combine(this IList source, bool pooled, params IList[] arrays) { var totalLength = source.Count; foreach (var arr in arrays) { totalLength += arr.Count; } if (totalLength == 0) { return Array.Empty(); } var combined = pooled ? STArrayPool.Shared.Rent(totalLength) : new T[totalLength]; source.CopyTo(combined, 0); var position = source.Count; foreach (var arr in arrays) { arr.CopyTo(combined, position); position += arr.Count; } return combined; } public static Point3D GetValidLocation(Map map, Point3D center, int range, int retries = 10) { if (map == null) { return center; } var loc = new Point3D(center.Z, center.Y, center.Z); for (var i = 0; i < retries; i++) { loc.X = center.X + (Random(range * 2 + 1) - range); loc.Y = center.Y + (Random(range * 2 + 1) - range); loc.Z = center.Z; if (map.CanSpawnMobile(loc)) { return loc; } loc.Z = map.GetAverageZ(loc.X, loc.Y); if (map.CanSpawnMobile(loc)) { return loc; } } return center; } public static Point3D GetValidLocationInLOS(Map map, Mobile from, int range, int retries = 10) { if (map == null) { return from.Location; } var center = from.Location; var loc = center; for (var i = 0; i < retries; i++) { loc.X = center.X + (Random(range * 2 + 1) - range); loc.Y = center.Y + (Random(range * 2 + 1) - range); loc.Z = center.Z; if (map.CanSpawnMobile(loc) && map.LineOfSight(from, loc)) { return loc; } loc.Z = map.GetAverageZ(loc.X, loc.Y); if (map.CanSpawnMobile(loc) && map.LineOfSight(from, loc)) { return loc; } } return center; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetUnderlyingNumericBitLength(this TypeCode typeCode) => typeCode switch { TypeCode.Byte => 8, TypeCode.SByte => 8, TypeCode.Int16 => 16, TypeCode.UInt16 => 16, TypeCode.Char => 16, TypeCode.Int32 => 32, TypeCode.UInt32 => 32, TypeCode.Int64 => 64, TypeCode.UInt64 => 64, _ => 64 }; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNullOrWhiteSpace(this ReadOnlySpan span) => span.IsEmpty || span.IsWhiteSpace(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InTypeList(this T obj, Type[] types) => obj.GetType().InTypeList(types); public static bool InTypeList(this Type t, Type[] types) { for (var i = 0; i < types.Length; ++i) { if (types[i].IsAssignableFrom(t)) { return true; } } return false; } public static int C16232(this int c16) { c16 &= 0x7FFF; var r = ((c16 >> 10) & 0x1F) << 3; var g = ((c16 >> 05) & 0x1F) << 3; var b = (c16 & 0x1F) << 3; return (r << 16) | (g << 8) | b; } public static int C16216(this int c16) => c16 & 0x7FFF; public static int C32216(this int c32) { c32 &= 0xFFFFFF; var r = ((c32 >> 16) & 0xFF) >> 3; var g = ((c32 >> 08) & 0xFF) >> 3; var b = (c32 & 0xFF) >> 3; return (r << 10) | (g << 5) | b; } public static void AddOrUpdate(this ConditionalWeakTable table, TKey key, TValue value) where TKey : class where TValue : class { table.Remove(key); table.Add(key, value); } }