diff --git a/Projects/Server/Map.cs b/Projects/Server/Map.cs index 89d417307..536e40c9c 100644 --- a/Projects/Server/Map.cs +++ b/Projects/Server/Map.cs @@ -1077,7 +1077,7 @@ namespace Server surface = id.Surface; impassable = id.Impassable; - if ((surface || impassable || (checkBlocksFit && item.BlocksFit)) && item.Z + id.CalcHeight > z && + if ((surface || impassable || checkBlocksFit && item.BlocksFit) && item.Z + id.CalcHeight > z && z + height > item.Z) return false; @@ -1148,7 +1148,7 @@ namespace Server var end = dest; - if (org.X > dest.X || (org.X == dest.X && org.Y > dest.Y) || (org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z)) + if (org.X > dest.X || org.X == dest.X && org.Y > dest.Y || org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z) { var swap = org; org = dest; diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 3a4ad1f92..3c8fa6513 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -849,23 +849,80 @@ namespace Server return total + bonus; } - public static void Shuffle(IList list) + public static void Shuffle(this IList list) { var count = list.Count; - for (var i = count - 1; i > 0; i--) + for (var i = 0; i < count; i++) { - var r = RandomSources.Source.Next(count); + var r = RandomMinMax(i, count - 1); var swap = list[r]; list[r] = list[i]; list[i] = swap; } } + public static void Shuffle(this Span list) + { + var count = list.Length; + for (var i = 0; i < count; i++) + { + var r = RandomMinMax(i, count - 1); + var swap = list[r]; + list[r] = list[i]; + list[i] = swap; + } + } + + /** + * 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]; + var sampleList = new T[count]; + + int 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 new List(); + + var length = source.Count; + Span list = stackalloc bool[length]; + var sampleList = new List(count); + + int i = 0; + do + { + var rand = Random(length); + if (!(list[rand] && (list[rand] = true))) + sampleList[i++] = source[rand]; + } while (i < count); + + return sampleList; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomList(params T[] list) => list.RandomElement(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T RandomElement(this IList list) => list.Count == 0 ? default : list[Random(list.Count)]; + public static T RandomElement(this IList list) => list.RandomElement(default); + + [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() => RandomSources.Source.NextBool(); diff --git a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs index 0cce376ae..cf3a5ea0b 100644 --- a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs +++ b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs @@ -346,34 +346,26 @@ namespace Server.Engines.Doom ResetLevers(); } - public virtual void GenKey() /* Shuffle & build key */ + public virtual void GenKey() { - ushort[] CA = { 1, 2, 4, 8 }; - for (int i = 0; i < 4; i++) - { - int n = (n = Utility.Random(0, 3)) == i ? n & ~i : n; - ushort tmp = CA[i]; - CA[i] = CA[n]; - CA[n] = tmp; - } + Span ca = stackalloc ushort[]{ 1, 2, 4, 8 }; + ca.Shuffle(); - for (int i = 0; i < 4; MyKey = (ushort)(CA[i++] | (MyKey <<= 4))) - { - } + for (int i = 0; i < 4; i++) MyKey = (ushort)(ca[i] | (MyKey <<= 4)); } private static bool IsValidDamagable(Mobile m) => m?.Deleted == false && - ((m.Player && m.Alive) || - (m is BaseCreature bc && (bc.Controlled || bc.Summoned) && !bc.IsDeadBondedPet)); + (m.Player && m.Alive || + m is BaseCreature bc && (bc.Controlled || bc.Summoned) && !bc.IsDeadBondedPet); public static void MoveMobileOut(Mobile m) { if (m != null) { - if (m is PlayerMobile && !m.Alive) - if (m.Corpse?.Deleted == false) - m.Corpse.MoveToWorld(lr_Exit, Map.Malas); + if (m is PlayerMobile && !m.Alive && m.Corpse?.Deleted == false) + m.Corpse.MoveToWorld(lr_Exit, Map.Malas); + BaseCreature.TeleportPets(m, lr_Exit, Map.Malas); m.Location = lr_Exit; m.ProcessDelta(); diff --git a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs index d8febd7ed..82661e851 100644 --- a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs +++ b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs @@ -335,8 +335,7 @@ namespace Server.Engines.Harvest public virtual void DoHarvestingSound(Mobile from, Item tool, HarvestDefinition def, object toHarvest) { - if (def.EffectSounds.Length > 0) - from.PlaySound(Utility.RandomList(def.EffectSounds)); + from.PlaySound(def.EffectSounds.RandomElement(-1)); } public virtual void DoHarvestingEffect(Mobile from, Item tool, HarvestDefinition def, Map map, Point3D loc) @@ -344,7 +343,7 @@ namespace Server.Engines.Harvest from.Direction = from.GetDirectionTo(loc); if (!from.Mounted) - from.Animate(Utility.RandomList(def.EffectActions), 5, 1, true, false, 0); + from.Animate(def.EffectActions.RandomElement(), 5, 1, true, false, 0); } public virtual HarvestDefinition GetDefinition() => Definitions.First(); diff --git a/Projects/UOContent/Engines/Harvest/Core/HarvestTimer.cs b/Projects/UOContent/Engines/Harvest/Core/HarvestTimer.cs index 92bf73683..2486588cf 100644 --- a/Projects/UOContent/Engines/Harvest/Core/HarvestTimer.cs +++ b/Projects/UOContent/Engines/Harvest/Core/HarvestTimer.cs @@ -22,7 +22,7 @@ namespace Server.Engines.Harvest m_Definition = def; m_ToHarvest = toHarvest; m_Locked = locked; - m_Count = Utility.RandomList(def.EffectCounts); + m_Count = def.EffectCounts.RandomElement(); } protected override void OnTick() @@ -31,4 +31,4 @@ namespace Server.Engines.Harvest Stop(); } } -} \ No newline at end of file +} diff --git a/Projects/UOContent/Engines/Harvest/Fishing.cs b/Projects/UOContent/Engines/Harvest/Fishing.cs index 51b3d5f6f..52f53df46 100644 --- a/Projects/UOContent/Engines/Harvest/Fishing.cs +++ b/Projects/UOContent/Engines/Harvest/Fishing.cs @@ -205,7 +205,7 @@ namespace Server.Engines.Harvest 0x1CE2, 0x1CEC // leg }; - preLoot = new ShipwreckedItem(Utility.RandomList(list)); + preLoot = new ShipwreckedItem(list.RandomElement()); break; } case 1: // Bone parts @@ -217,7 +217,7 @@ namespace Server.Engines.Harvest 0x1B15, 0x1B16 // pelvis bones }; - preLoot = new ShipwreckedItem(Utility.RandomList(list)); + preLoot = new ShipwreckedItem(list.RandomElement()); break; } case 2: // Paintings and portraits @@ -258,7 +258,7 @@ namespace Server.Engines.Harvest if (Utility.Random(list.Length + 1) == 0) preLoot = new Candelabra(); else - preLoot = new ShipwreckedItem(Utility.RandomList(list)); + preLoot = new ShipwreckedItem(list.RandomElement()); break; } diff --git a/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs b/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs index 067319b75..48ef5c891 100644 --- a/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs +++ b/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Server.Mobiles; namespace Server.Engines.Quests.Collector @@ -56,8 +55,6 @@ namespace Server.Engines.Quests.Collector new ImageTypeInfo(9746, typeof(Juggernaut), 55, 38) }; - private static ImageType[] m_ImageTypeList; - public ImageTypeInfo(int figurine, Type type, int x, int y) { Figurine = figurine; @@ -66,6 +63,8 @@ namespace Server.Engines.Quests.Collector Y = y; } + public ImageType Image { get; } + public int Figurine { get; } public Type Type { get; } @@ -82,16 +81,21 @@ namespace Server.Engines.Quests.Collector public static ImageType[] RandomList(int count) { - if (m_ImageTypeList == null) - { - m_ImageTypeList = new ImageType[m_Table.Length]; - for (int i = 0; i < m_Table.Length; i++) - m_ImageTypeList[i] = (ImageType)i; - } + if (count <= 0) return Array.Empty(); - ImageType[] array = m_ImageTypeList.ToArray(); - Utility.Shuffle(array); - return array.Take(count).ToArray(); + var length = m_Table.Length; + Span list = stackalloc bool[length]; + var imageTypes = new ImageType[count]; + + int i = 0; + do + { + var rand = Utility.Random(length); + if (!(list[rand] && (list[rand] = true))) + imageTypes[i++] = (ImageType)rand; + } while (i < count); + + return imageTypes; } } -} \ No newline at end of file +} diff --git a/Projects/UOContent/Engines/Quests/Emino's Undertaking/Mobiles/HiddenFigure.cs b/Projects/UOContent/Engines/Quests/Emino's Undertaking/Mobiles/HiddenFigure.cs index 5757f2d90..0de8a29c6 100644 --- a/Projects/UOContent/Engines/Quests/Emino's Undertaking/Mobiles/HiddenFigure.cs +++ b/Projects/UOContent/Engines/Quests/Emino's Undertaking/Mobiles/HiddenFigure.cs @@ -13,7 +13,7 @@ namespace Server.Engines.Quests.Ninja }; [Constructible] - public HiddenFigure() => Message = Utility.RandomList(Messages); + public HiddenFigure() => Message = Messages.RandomElement(); public HiddenFigure(Serial serial) : base(serial) { @@ -83,4 +83,4 @@ namespace Server.Engines.Quests.Ninja Message = reader.ReadInt(); } } -} \ No newline at end of file +} diff --git a/Projects/UOContent/Items/Containers/FillableContainers.cs b/Projects/UOContent/Items/Containers/FillableContainers.cs index 4621f2d34..28b7c0ca9 100644 --- a/Projects/UOContent/Items/Containers/FillableContainers.cs +++ b/Projects/UOContent/Items/Containers/FillableContainers.cs @@ -599,8 +599,7 @@ namespace Server.Items Item item = Loot.Construct(m_Types); if (item is Key key) - key.ItemID = Utility.RandomList((int)KeyType.Copper, (int)KeyType.Gold, (int)KeyType.Iron, - (int)KeyType.Rusty); + key.ItemID = Utility.RandomList((int)KeyType.Copper, (int)KeyType.Gold, (int)KeyType.Iron, (int)KeyType.Rusty); else if (item is Arrow || item is Bolt) item.Amount = Utility.RandomMinMax(2, 6); else if (item is Bandage || item is Lockpick) diff --git a/Projects/UOContent/Items/Containers/ParagonChest.cs b/Projects/UOContent/Items/Containers/ParagonChest.cs index aa02fa032..112d2ad92 100644 --- a/Projects/UOContent/Items/Containers/ParagonChest.cs +++ b/Projects/UOContent/Items/Containers/ParagonChest.cs @@ -17,10 +17,10 @@ namespace Server.Items private string m_Name; [Constructible] - public ParagonChest(string name, int level) : base(Utility.RandomList(m_ItemIDs)) + public ParagonChest(string name, int level) : base(m_ItemIDs.RandomElement()) { m_Name = name; - Hue = Utility.RandomList(m_Hues); + Hue = m_Hues.RandomElement(); Fill(level); } @@ -207,4 +207,4 @@ namespace Server.Items m_Name = Utility.Intern(reader.ReadString()); } } -} \ No newline at end of file +} diff --git a/Projects/UOContent/Items/Misc/Prism of Light/ShimmeringCrystal.cs b/Projects/UOContent/Items/Misc/Prism of Light/ShimmeringCrystal.cs index e5d7e8b55..31f3604f8 100644 --- a/Projects/UOContent/Items/Misc/Prism of Light/ShimmeringCrystal.cs +++ b/Projects/UOContent/Items/Misc/Prism of Light/ShimmeringCrystal.cs @@ -11,7 +11,7 @@ namespace Server.Items }; [Constructible] - public ShimmeringCrystals() : base(Utility.RandomList(m_ItemIDs)) + public ShimmeringCrystals() : base(m_ItemIDs.RandomElement()) { } @@ -36,4 +36,4 @@ namespace Server.Items int version = reader.ReadInt(); } } -} \ No newline at end of file +} diff --git a/Projects/UOContent/Items/Skill Items/Fishing/Misc/SpecialFishingNet.cs b/Projects/UOContent/Items/Skill Items/Fishing/Misc/SpecialFishingNet.cs index 07355ee3c..f0b271bed 100644 --- a/Projects/UOContent/Items/Skill Items/Fishing/Misc/SpecialFishingNet.cs +++ b/Projects/UOContent/Items/Skill Items/Fishing/Misc/SpecialFishingNet.cs @@ -43,7 +43,7 @@ namespace Server.Items Weight = 1.0; if (Utility.RandomDouble() < 0.01) - Hue = Utility.RandomList(m_Hues); + Hue = m_Hues.RandomElement(); else Hue = 0x8A0; } @@ -405,4 +405,4 @@ namespace Server.Items int version = reader.ReadInt(); } } -} \ No newline at end of file +} diff --git a/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastOrgans.cs b/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastOrgans.cs index e32cea45c..99bb10b33 100644 --- a/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastOrgans.cs +++ b/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastOrgans.cs @@ -227,7 +227,7 @@ namespace Server.Items public override void Initialize() { - Hue = Utility.RandomList(m_Hues); + Hue = m_Hues.RandomElement(); AddComponent(new PlagueBeastComponent(0x3BB, Hue), 0, 0); AddComponent(new PlagueBeastComponent(0x3BA, Hue), 4, 6); @@ -264,13 +264,13 @@ namespace Server.Items Opened = true; } - private static int RandomHue(int exculde) + private static int RandomHue(int exclude) { for (int i = 0; i < 20; i++) { - int hue = Utility.RandomList(m_Hues); + int hue = m_Hues.RandomElement(); - if (hue != exculde) + if (hue != exclude) return hue; } diff --git a/Projects/UOContent/Items/Special/Veteran Rewards/Brazier.cs b/Projects/UOContent/Items/Special/Veteran Rewards/Brazier.cs index c295c2ff3..5b43867cd 100644 --- a/Projects/UOContent/Items/Special/Veteran Rewards/Brazier.cs +++ b/Projects/UOContent/Items/Special/Veteran Rewards/Brazier.cs @@ -17,7 +17,7 @@ namespace Server.Items private bool m_IsRewardItem; [Constructible] - public RewardBrazier() : this(Utility.RandomList(m_Art)) + public RewardBrazier() : this(m_Art.RandomElement()) { } diff --git a/Projects/UOContent/Items/Special/Veteran Rewards/Cannon.cs b/Projects/UOContent/Items/Special/Veteran Rewards/Cannon.cs index a24287e8e..729bee365 100644 --- a/Projects/UOContent/Items/Special/Veteran Rewards/Cannon.cs +++ b/Projects/UOContent/Items/Special/Veteran Rewards/Cannon.cs @@ -208,14 +208,13 @@ namespace Server.Items return; Effects.PlaySound(target, map, Utility.RandomList(0x11B, 0x11C, 0x11D)); - Effects.SendLocationEffect(target, map, Utility.RandomList(m_Effects), 16, 1); + Effects.SendLocationEffect(target, map, m_Effects.RandomElement(), 16, 1); for (int count = Utility.Random(3); count > 0; count--) { IPoint3D location = new Point3D(target.X + Utility.RandomMinMax(-1, 1), target.Y + Utility.RandomMinMax(-1, 1), target.Z); - int effect = Utility.RandomList(m_Effects); - Effects.SendLocationEffect(location, map, effect, 16, 1); + Effects.SendLocationEffect(location, map, m_Effects.RandomElement(), 16, 1); } Charges -= 1; diff --git a/Projects/UOContent/Items/Talismans/BaseTalisman.cs b/Projects/UOContent/Items/Talismans/BaseTalisman.cs index 487c0983e..9be56563a 100644 --- a/Projects/UOContent/Items/Talismans/BaseTalisman.cs +++ b/Projects/UOContent/Items/Talismans/BaseTalisman.cs @@ -903,7 +903,7 @@ namespace Server.Items 0x2F58, 0x2F59, 0x2F5A, 0x2F5B }; - public static int GetRandomItemID() => Utility.RandomList(m_ItemIDs); + public static int GetRandomItemID() => m_ItemIDs.RandomElement(); private static readonly Type[] m_Summons = { diff --git a/Projects/UOContent/Mobiles/Special/BaseChampion.cs b/Projects/UOContent/Mobiles/Special/BaseChampion.cs index 1b28ba168..b30723336 100644 --- a/Projects/UOContent/Mobiles/Special/BaseChampion.cs +++ b/Projects/UOContent/Mobiles/Special/BaseChampion.cs @@ -129,13 +129,7 @@ namespace Server.Mobiles } // Randomize - for (int i = 0; i < toGive.Count; ++i) - { - int rand = Utility.Random(toGive.Count); - Mobile hold = toGive[i]; - toGive[i] = toGive[rand]; - toGive[rand] = hold; - } + toGive.Shuffle(); for (int i = 0; i < 6; ++i) { diff --git a/Projects/UOContent/Mobiles/Special/Harrower.cs b/Projects/UOContent/Mobiles/Special/Harrower.cs index 7ba422e6f..ace77cfcf 100644 --- a/Projects/UOContent/Mobiles/Special/Harrower.cs +++ b/Projects/UOContent/Mobiles/Special/Harrower.cs @@ -248,14 +248,7 @@ namespace Server.Mobiles if (toGive.Count == 0) return; - // Randomize - for (int i = 0; i < toGive.Count; ++i) - { - int rand = Utility.Random(toGive.Count); - Mobile hold = toGive[i]; - toGive[i] = toGive[rand]; - toGive[rand] = hold; - } + toGive.Shuffle(); for (int i = 0; i < 16; ++i) {