diff --git a/Projects/Server/Random/BuiltInRng.cs b/Projects/Server/Random/BuiltInRng.cs index 31d52b590..d3927242d 100644 --- a/Projects/Server/Random/BuiltInRng.cs +++ b/Projects/Server/Random/BuiltInRng.cs @@ -39,6 +39,9 @@ public static class BuiltInRng [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long Next(long minValue, long count) => minValue + Generator.NextInt64(count); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long NextLong() => Generator.NextInt64(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double NextDouble() => Generator.NextDouble(); diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 37124526c..837cd92b5 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -12,7 +12,6 @@ using System.Text; using System.Xml; using Server.Buffers; using Server.Collections; -using Server.Logging; using Server.Random; using Server.Text; @@ -20,8 +19,6 @@ namespace Server; public static class Utility { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(Utility)); - private static Dictionary _ipAddressTable; private static SkillName[] _allSkills = @@ -679,6 +676,50 @@ public static class Utility [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InUpdateRange(Point3D p1, Point3D p2) => InRange(p1, p2, 18); + // 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) @@ -686,11 +727,19 @@ public static class Utility return 0; } - var total = 0; + int total; - for (var i = 0; i < amount; ++i) + if (sides == 2) { - total += BuiltInRng.Next(1, sides); + total = CoinFlips(amount); + } + else + { + total = 0; + for (var i = 0; i < amount; ++i) + { + total += BuiltInRng.Next(1, sides); + } } return total + bonus; @@ -730,8 +779,9 @@ public static class Utility do { var rand = Random(length); - if (!(list[rand] && (list[rand] = true))) + if (!list[rand]) { + list[rand] = true; sampleList[i++] = source[rand]; } } while (i < count); @@ -756,8 +806,9 @@ public static class Utility do { var rand = Random(length); - if (!(list[rand] && (list[rand] = true))) + if (!list[rand]) { + list[rand] = true; sampleList[i++] = source[rand]; } } while (i < count); @@ -780,8 +831,9 @@ public static class Utility do { var rand = Random(length); - if (!(list[rand] && (list[rand] = true))) + if (!list[rand]) { + list[rand] = true; dest.Add(source[rand]); } } while (++i < count); diff --git a/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs b/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs index 188b6a353..fa95f58bc 100644 --- a/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs +++ b/Projects/UOContent/Engines/Quests/Collector/Items/ImageTypeInfo.cs @@ -30,34 +30,38 @@ public enum ImageType public class ImageTypeInfo { - private static readonly ImageTypeInfo[] m_Table = - { - new(9734, typeof(Betrayer), 75, 45), - new(9735, typeof(Bogling), 75, 45), - new(9736, typeof(BogThing), 60, 47), - new(9615, typeof(Gazer), 75, 45), - new(9743, typeof(Beetle), 60, 55), - new(9667, typeof(GiantBlackWidow), 55, 52), - new(9657, typeof(Scorpion), 65, 47), - new(9758, typeof(JukaMage), 75, 45), - new(9759, typeof(JukaWarrior), 75, 45), - new(9636, typeof(Lich), 75, 45), - new(9756, typeof(MeerMage), 75, 45), - new(9757, typeof(MeerWarrior), 75, 45), - new(9638, typeof(Mongbat), 70, 50), - new(9639, typeof(Mummy), 75, 45), - new(9654, typeof(Pixie), 75, 45), - new(9747, typeof(PlagueBeast), 60, 45), - new(9750, typeof(SandVortex), 60, 43), - new(9614, typeof(StoneGargoyle), 75, 45), - new(9753, typeof(SwampDragon), 50, 55), - new(8448, typeof(Wisp), 75, 45), - new(9746, typeof(Juggernaut), 55, 38) - }; + private static readonly ImageTypeInfo[] _table = + [ + new ImageTypeInfo(9734, typeof(Betrayer), ImageType.Betrayer, 75, 45), + new ImageTypeInfo(9735, typeof(Bogling), ImageType.Bogling, 75, 45), + new ImageTypeInfo(9736, typeof(BogThing), ImageType.BogThing, 60, 47), + new ImageTypeInfo(9615, typeof(Gazer), ImageType.Gazer, 75, 45), + new ImageTypeInfo(9743, typeof(Beetle), ImageType.Beetle, 60, 55), + new ImageTypeInfo(9667, typeof(GiantBlackWidow), ImageType.GiantBlackWidow, 55, 52), + new ImageTypeInfo(9657, typeof(Scorpion), ImageType.Scorpion, 65, 47), + new ImageTypeInfo(9758, typeof(JukaMage), ImageType.JukaMage, 75, 45), + new ImageTypeInfo(9759, typeof(JukaWarrior), ImageType.JukaWarrior, 75, 45), + new ImageTypeInfo(9636, typeof(Lich), ImageType.Lich, 75, 45), + new ImageTypeInfo(9756, typeof(MeerMage), ImageType.MeerMage, 75, 45), + new ImageTypeInfo(9757, typeof(MeerWarrior), ImageType.MeerWarrior, 75, 45), + new ImageTypeInfo(9638, typeof(Mongbat), ImageType.Mongbat, 70, 50), + new ImageTypeInfo(9639, typeof(Mummy), ImageType.Mummy, 75, 45), + new ImageTypeInfo(9654, typeof(Pixie), ImageType.Pixie, 75, 45), + new ImageTypeInfo(9747, typeof(PlagueBeast), ImageType.PlagueBeast, 60, 45), + new ImageTypeInfo(9750, typeof(SandVortex), ImageType.SandVortex, 60, 43), + new ImageTypeInfo(9614, typeof(StoneGargoyle), ImageType.StoneGargoyle, 75, 45), + new ImageTypeInfo(9753, typeof(SwampDragon), ImageType.SwampDragon, 50, 55), + new ImageTypeInfo(8448, typeof(Wisp), ImageType.Wisp, 75, 45), + new ImageTypeInfo(9746, typeof(Juggernaut), ImageType.Juggernaut, 55, 38) + ]; - public ImageTypeInfo(int figurine, Type type, int x, int y) + // Used for sampling + private static readonly ImageTypeInfo[] _shuffleTable = (ImageTypeInfo[])_table.Clone(); + + public ImageTypeInfo(int figurine, Type type, ImageType image, int x, int y) { Figurine = figurine; + Image = image; Type = type; X = x; Y = y; @@ -65,6 +69,8 @@ public class ImageTypeInfo public int Figurine { get; } + public ImageType Image { get; } + public Type Type { get; } public int Name => Figurine < 0x4000 ? 1020000 + Figurine : 1078872 + Figurine; @@ -74,7 +80,7 @@ public class ImageTypeInfo public static ImageTypeInfo Get(ImageType image) { var index = (int)image; - return m_Table[index >= 0 && index < m_Table.Length ? index : 0]; + return _table[index >= 0 && index < _table.Length ? index : 0]; } public static ImageType[] RandomList(int count) @@ -84,21 +90,14 @@ public class ImageTypeInfo return Array.Empty(); } - var length = m_Table.Length; - Span list = stackalloc bool[length]; - list.Clear(); - + _shuffleTable.Shuffle(); var imageTypes = new ImageType[count]; - var i = 0; - do + var minCount = Math.Min(count, _shuffleTable.Length); + for (var i = 0; i < minCount; i++) { - var rand = Utility.Random(length); - if (!(list[rand] && (list[rand] = true))) - { - imageTypes[i++] = (ImageType)rand; - } - } while (i < count); + imageTypes[i] = _shuffleTable[i].Image; + } return imageTypes; } diff --git a/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs b/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs index 0480c1dc0..ba49d027c 100644 --- a/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs +++ b/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs @@ -72,7 +72,7 @@ public partial class DawnsMusicBox : Item, ISecurable { MusicName.ValoriaShips, new DawnsMusicInfo(1075140, DawnsMusicRarity.Rare) } }; - public static readonly MusicName[] _commonTracks = + private static readonly MusicName[] _commonTracks = { MusicName.Samlethe, MusicName.Sailing, MusicName.Britain2, MusicName.Britain1, MusicName.Bucsden, MusicName.Forest_a, MusicName.Cove, MusicName.Death, @@ -86,7 +86,7 @@ public partial class DawnsMusicBox : Item, ISecurable MusicName.Mountn_a, MusicName.Wind, MusicName.Yew, MusicName.Zento }; - public static readonly MusicName[] _uncommonTracks = + private static readonly MusicName[] _uncommonTracks = { MusicName.GwennoConversation, MusicName.DreadHornArea, MusicName.ElfCity, MusicName.GoodEndGame, MusicName.GoodVsEvil, MusicName.GreatEarthSerpents, @@ -94,7 +94,7 @@ public partial class DawnsMusicBox : Item, ISecurable MusicName.MinocNegative, MusicName.ParoxysmusLair, MusicName.Paws }; - public static readonly MusicName[] _rareTracks = + private static readonly MusicName[] _rareTracks = { MusicName.SelimsBar, MusicName.SerpentIsleCombat_U7, MusicName.ValoriaShips }; @@ -117,9 +117,16 @@ public partial class DawnsMusicBox : Item, ISecurable { Weight = 1.0; - _tracks = new List(); + var shuffledTracks = GetTracks(DawnsMusicRarity.Common); + shuffledTracks.Shuffle(); - GetTracks(DawnsMusicRarity.Common).RandomSample(4, _tracks); + _tracks = + [ + shuffledTracks[0], + shuffledTracks[1], + shuffledTracks[2], + shuffledTracks[3] + ]; } public override int LabelNumber => 1075198; // Dawn's Music Box @@ -131,8 +138,7 @@ public partial class DawnsMusicBox : Item, ISecurable return; } - box.Tracks = new List(); - box.Tracks.AddRange(Tracks); + box.Tracks = [..Tracks]; } public override void GetProperties(IPropertyList list) @@ -257,7 +263,7 @@ public partial class DawnsMusicBox : Item, ISecurable private void Deserialize(IGenericReader reader, int version) { var count = reader.ReadInt(); - _tracks = new List(); + _tracks = []; for (var i = 0; i < count; i++) { diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 03cf7e43c..fb579d09c 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -173,6 +173,8 @@ namespace Server.Mobiles } public const int MaxLoyalty = 100; + public const int LoyaltyIncreasePerFood = 10; + public const int MaxLoyaltyIncrease = MaxLoyalty / LoyaltyIncreasePerFood; public const int MaxOwners = 5; @@ -4180,91 +4182,87 @@ namespace Server.Mobiles public virtual bool CheckFeed(Mobile from, Item dropped) { - if (!IsDeadPet && Controlled && (ControlMaster == from || IsPetFriend(from))) + if (IsDeadPet || !Controlled || (ControlMaster != from && !IsPetFriend(from))) { - if (CheckFoodPreference(dropped)) + return false; + } + + if (!CheckFoodPreference(dropped)) + { + return false; + } + + var amount = dropped.Amount; + + if (amount > 0) + { + int stamGain = dropped switch { - var amount = dropped.Amount; + Gold => amount - 50, + _ => amount * 15 - 50 + }; - if (amount > 0) + if (stamGain > 0) + { + Stam += stamGain; + // 64 food = 3,640 steps + StaminaSystem.RegenSteps(this as IHasSteps, stamGain * 4); + } + + if (Core.SE) + { + m_Loyalty = MaxLoyalty; + } + else if (m_Loyalty < MaxLoyalty) + { + // 50% chance to increase 10 loyalty per food + m_Loyalty = Math.Min(MaxLoyalty, Utility.CoinFlips(amount, MaxLoyaltyIncrease) * 10); + } + + /* if (happier )*/ + // looks like in OSI pets say they are happier even if they are at maximum loyalty + SayTo(from, 502060); // Your pet looks happier. + + if (Body.IsAnimal) + { + Animate(3, 5, 1, true, false, 0); + } + else if (Body.IsMonster) + { + Animate(17, 5, 1, true, false, 0); + } + + if (IsBondable && !IsBonded) + { + var master = m_ControlMaster; + + if (master != null && master == from) // So friends can't start the bonding process { - int stamGain = dropped switch + if (MinTameSkill <= 29.1 || master.Skills.AnimalTaming.Base >= MinTameSkill || + OverrideBondingReqs() || + Core.ML && master.Skills.AnimalTaming.Value >= MinTameSkill) { - Gold => amount - 50, - _ => amount * 15 - 50 - }; - - if (stamGain > 0) - { - Stam += stamGain; - // 64 food = 3,640 steps - StaminaSystem.RegenSteps(this as IHasSteps, stamGain * 4); - } - - if (Core.SE) - { - if (m_Loyalty < MaxLoyalty) + if (BondingBegin == DateTime.MinValue) { - m_Loyalty = MaxLoyalty; + BondingBegin = Core.Now; + } + else if (BondingBegin + BondingDelay <= Core.Now) + { + IsBonded = true; + BondingBegin = DateTime.MinValue; + from.SendLocalizedMessage(1049666); // Your pet has bonded with you! } } - else + else if (Core.ML) { - for (var i = 0; i < amount; ++i) - { - if (m_Loyalty < MaxLoyalty && Utility.RandomBool()) - { - m_Loyalty += 10; - } - } + // Your pet cannot form a bond with you until your animal taming ability has risen. + from.SendLocalizedMessage(1075268); } - - /* if (happier )*/ - // looks like in OSI pets say they are happier even if they are at maximum loyalty - SayTo(from, 502060); // Your pet looks happier. - - if (Body.IsAnimal) - { - Animate(3, 5, 1, true, false, 0); - } - else if (Body.IsMonster) - { - Animate(17, 5, 1, true, false, 0); - } - - if (IsBondable && !IsBonded) - { - var master = m_ControlMaster; - - if (master != null && master == from) // So friends can't start the bonding process - { - if (MinTameSkill <= 29.1 || master.Skills.AnimalTaming.Base >= MinTameSkill || - OverrideBondingReqs() || - Core.ML && master.Skills.AnimalTaming.Value >= MinTameSkill) - { - if (BondingBegin == DateTime.MinValue) - { - BondingBegin = Core.Now; - } - else if (BondingBegin + BondingDelay <= Core.Now) - { - IsBonded = true; - BondingBegin = DateTime.MinValue; - from.SendLocalizedMessage(1049666); // Your pet has bonded with you! - } - } - else if (Core.ML) - { - // Your pet cannot form a bond with you until your animal taming ability has risen. - from.SendLocalizedMessage(1075268); - } - } - } - - dropped.Delete(); - return true; } } + + dropped.Delete(); + return true; } return false;