diff --git a/Distribution/Data/skills.json b/Distribution/Data/skills.json index 3349f359a..f3e4b28eb 100644 --- a/Distribution/Data/skills.json +++ b/Distribution/Data/skills.json @@ -11,7 +11,9 @@ "DexGain": 0.5, "IntGain": 0.5, "GainFactor": 1, - "ProfessionSkillName": "Alchemy" + "ProfessionSkillName": "Alchemy", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 1, @@ -25,7 +27,9 @@ "DexGain": 0.15, "IntGain": 0.7, "GainFactor": 1, - "ProfessionSkillName": "Anatomy" + "ProfessionSkillName": "Anatomy", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 2, @@ -39,7 +43,9 @@ "DexGain": 0, "IntGain": 1, "GainFactor": 1, - "ProfessionSkillName": "AnimalLore" + "ProfessionSkillName": "AnimalLore", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 3, @@ -53,7 +59,9 @@ "DexGain": 0, "IntGain": 1, "GainFactor": 1, - "ProfessionSkillName": "ItemID" + "ProfessionSkillName": "ItemID", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 4, @@ -67,7 +75,9 @@ "DexGain": 0.15, "IntGain": 0.1, "GainFactor": 1, - "ProfessionSkillName": "ArmsLore" + "ProfessionSkillName": "ArmsLore", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 5, @@ -81,7 +91,9 @@ "DexGain": 0.25, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Parrying" + "ProfessionSkillName": "Parrying", + "PrimaryStat": "Dex", + "SecondaryStat": "Str" }, { "SkillID": 6, @@ -95,7 +107,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Begging" + "ProfessionSkillName": "Begging", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 7, @@ -109,7 +123,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Blacksmith" + "ProfessionSkillName": "Blacksmith", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 8, @@ -123,7 +139,9 @@ "DexGain": 1.6, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Bowcraft" + "ProfessionSkillName": "Bowcraft", + "PrimaryStat": "Dex", + "SecondaryStat": "Str" }, { "SkillID": 9, @@ -137,7 +155,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Peacemaking" + "ProfessionSkillName": "Peacemaking", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 10, @@ -151,7 +171,9 @@ "DexGain": 1.5, "IntGain": 1.5, "GainFactor": 1, - "ProfessionSkillName": "Camping" + "ProfessionSkillName": "Camping", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 11, @@ -165,7 +187,9 @@ "DexGain": 0.5, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Carpentry" + "ProfessionSkillName": "Carpentry", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 12, @@ -179,7 +203,9 @@ "DexGain": 0.75, "IntGain": 0.75, "GainFactor": 1, - "ProfessionSkillName": "Cartography" + "ProfessionSkillName": "Cartography", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 13, @@ -193,7 +219,9 @@ "DexGain": 2, "IntGain": 3, "GainFactor": 1, - "ProfessionSkillName": "Cooking" + "ProfessionSkillName": "Cooking", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 14, @@ -207,7 +235,9 @@ "DexGain": 0.4, "IntGain": 0.6, "GainFactor": 1, - "ProfessionSkillName": "DetectingHidden" + "ProfessionSkillName": "DetectingHidden", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 15, @@ -221,7 +251,9 @@ "DexGain": 0.25, "IntGain": 0.25, "GainFactor": 1, - "ProfessionSkillName": "Enticement" + "ProfessionSkillName": "Enticement", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 16, @@ -235,7 +267,9 @@ "DexGain": 0, "IntGain": 1, "GainFactor": 1, - "ProfessionSkillName": "EvaluateIntelligence" + "ProfessionSkillName": "EvaluateIntelligence", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 17, @@ -249,7 +283,9 @@ "DexGain": 0.6, "IntGain": 0.8, "GainFactor": 1, - "ProfessionSkillName": "Healing" + "ProfessionSkillName": "Healing", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 18, @@ -263,7 +299,9 @@ "DexGain": 0.5, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Fishing" + "ProfessionSkillName": "Fishing", + "PrimaryStat": "Dex", + "SecondaryStat": "Str" }, { "SkillID": 19, @@ -277,7 +315,9 @@ "DexGain": 0.2, "IntGain": 0.8, "GainFactor": 1, - "ProfessionSkillName": "ForensicEvaluation" + "ProfessionSkillName": "ForensicEvaluation", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 20, @@ -291,7 +331,9 @@ "DexGain": 0.625, "IntGain": 0.25, "GainFactor": 1, - "ProfessionSkillName": "Herding" + "ProfessionSkillName": "Herding", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 21, @@ -305,7 +347,9 @@ "DexGain": 0.8, "IntGain": 0.2, "GainFactor": 1, - "ProfessionSkillName": "Hiding" + "ProfessionSkillName": "Hiding", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 22, @@ -319,7 +363,9 @@ "DexGain": 0.45, "IntGain": 0.05, "GainFactor": 1, - "ProfessionSkillName": "Provocation" + "ProfessionSkillName": "Provocation", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 23, @@ -333,7 +379,9 @@ "DexGain": 0.2, "IntGain": 0.8, "GainFactor": 1, - "ProfessionSkillName": "Inscription" + "ProfessionSkillName": "Inscription", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 24, @@ -347,7 +395,9 @@ "DexGain": 2, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Lockpicking" + "ProfessionSkillName": "Lockpicking", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 25, @@ -361,7 +411,9 @@ "DexGain": 0, "IntGain": 1.5, "GainFactor": 1, - "ProfessionSkillName": "Magery" + "ProfessionSkillName": "Magery", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 26, @@ -375,7 +427,9 @@ "DexGain": 0.25, "IntGain": 0.5, "GainFactor": 1, - "ProfessionSkillName": "ResistingSpells" + "ProfessionSkillName": "ResistingSpells", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 27, @@ -389,7 +443,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Tactics" + "ProfessionSkillName": "Tactics", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 28, @@ -403,7 +459,9 @@ "DexGain": 2.5, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Snooping" + "ProfessionSkillName": "Snooping", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 29, @@ -417,7 +475,9 @@ "DexGain": 0.8, "IntGain": 0.2, "GainFactor": 1, - "ProfessionSkillName": "Musicianship" + "ProfessionSkillName": "Musicianship", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 30, @@ -431,7 +491,9 @@ "DexGain": 0.4, "IntGain": 1.6, "GainFactor": 1, - "ProfessionSkillName": "Poisoning" + "ProfessionSkillName": "Poisoning", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 31, @@ -445,7 +507,9 @@ "DexGain": 0.75, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Archery" + "ProfessionSkillName": "Archery", + "PrimaryStat": "Dex", + "SecondaryStat": "Str" }, { "SkillID": 32, @@ -459,7 +523,9 @@ "DexGain": 0, "IntGain": 1, "GainFactor": 1, - "ProfessionSkillName": "SpiritSpeak" + "ProfessionSkillName": "SpiritSpeak", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 33, @@ -473,7 +539,9 @@ "DexGain": 1, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Stealing" + "ProfessionSkillName": "Stealing", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 34, @@ -487,7 +555,9 @@ "DexGain": 1.63, "IntGain": 0.5, "GainFactor": 1, - "ProfessionSkillName": "Tailoring" + "ProfessionSkillName": "Tailoring", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 35, @@ -501,7 +571,9 @@ "DexGain": 0.2, "IntGain": 0.4, "GainFactor": 1, - "ProfessionSkillName": "AnimalTaming" + "ProfessionSkillName": "AnimalTaming", + "PrimaryStat": "Str", + "SecondaryStat": "Int" }, { "SkillID": 36, @@ -515,7 +587,9 @@ "DexGain": 0, "IntGain": 0.8, "GainFactor": 1, - "ProfessionSkillName": "TasteIdentification" + "ProfessionSkillName": "TasteIdentification", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 37, @@ -529,7 +603,9 @@ "DexGain": 0.2, "IntGain": 0.3, "GainFactor": 1, - "ProfessionSkillName": "Tinkering" + "ProfessionSkillName": "Tinkering", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 38, @@ -543,7 +619,9 @@ "DexGain": 1.25, "IntGain": 1.25, "GainFactor": 1, - "ProfessionSkillName": "Tracking" + "ProfessionSkillName": "Tracking", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 39, @@ -557,7 +635,9 @@ "DexGain": 0.4, "IntGain": 0.8, "GainFactor": 1, - "ProfessionSkillName": "Veterinary" + "ProfessionSkillName": "Veterinary", + "PrimaryStat": "Int", + "SecondaryStat": "Dex" }, { "SkillID": 40, @@ -571,7 +651,9 @@ "DexGain": 0.25, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Swordsmanship" + "ProfessionSkillName": "Swordsmanship", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 41, @@ -585,7 +667,9 @@ "DexGain": 0.1, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "MaceFighting" + "ProfessionSkillName": "MaceFighting", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 42, @@ -599,7 +683,9 @@ "DexGain": 0.55, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Fencing" + "ProfessionSkillName": "Fencing", + "PrimaryStat": "Dex", + "SecondaryStat": "Str" }, { "SkillID": 43, @@ -613,7 +699,9 @@ "DexGain": 0.1, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Wrestling" + "ProfessionSkillName": "Wrestling", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 44, @@ -627,7 +715,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Lumberjacking" + "ProfessionSkillName": "Lumberjacking", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 45, @@ -641,7 +731,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Mining" + "ProfessionSkillName": "Mining", + "PrimaryStat": "Str", + "SecondaryStat": "Dex" }, { "SkillID": 46, @@ -655,7 +747,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Meditation" + "ProfessionSkillName": "Meditation", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 47, @@ -669,7 +763,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Stealth" + "ProfessionSkillName": "Stealth", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 48, @@ -683,7 +779,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Disarm" + "ProfessionSkillName": "Disarm", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 49, @@ -697,7 +795,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Necromancy" + "ProfessionSkillName": "Necromancy", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 50, @@ -711,7 +811,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Focus" + "ProfessionSkillName": "Focus", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 51, @@ -725,7 +827,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Chivalry" + "ProfessionSkillName": "Chivalry", + "PrimaryStat": "Str", + "SecondaryStat": "Int" }, { "SkillID": 52, @@ -739,7 +843,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Bushido" + "ProfessionSkillName": "Bushido", + "PrimaryStat": "Str", + "SecondaryStat": "Int" }, { "SkillID": 53, @@ -753,7 +859,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Ninjitsu" + "ProfessionSkillName": "Ninjitsu", + "PrimaryStat": "Dex", + "SecondaryStat": "Int" }, { "SkillID": 54, @@ -767,7 +875,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Spellweaving" + "ProfessionSkillName": "Spellweaving", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 55, @@ -781,7 +891,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Mysticism" + "ProfessionSkillName": "Mysticism", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 56, @@ -795,7 +907,9 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Imbuing" + "ProfessionSkillName": "Imbuing", + "PrimaryStat": "Int", + "SecondaryStat": "Str" }, { "SkillID": 57, @@ -809,6 +923,8 @@ "DexGain": 0, "IntGain": 0, "GainFactor": 1, - "ProfessionSkillName": "Throwing" + "ProfessionSkillName": "Throwing", + "PrimaryStat": "Dex", + "SecondaryStat": "Str" } -] \ No newline at end of file +] diff --git a/Projects/Server/Configuration/ServerConfiguration.cs b/Projects/Server/Configuration/ServerConfiguration.cs index 2cb1b6e69..e5d318996 100644 --- a/Projects/Server/Configuration/ServerConfiguration.cs +++ b/Projects/Server/Configuration/ServerConfiguration.cs @@ -64,6 +64,12 @@ public static class ServerConfiguration return bool.TryParse(strValue, out var value) ? value : defaultValue; } + public static TimeSpan GetSetting(string key, TimeSpan defaultValue) + { + _settings.Settings.TryGetValue(key, out var strValue); + return TimeSpan.TryParse(strValue, out var value) ? value : defaultValue; + } + public static T GetSetting(string key, T defaultValue) where T : struct, Enum { _settings.Settings.TryGetValue(key, out var strValue); diff --git a/Projects/Server/Skills.cs b/Projects/Server/Skills.cs index b4ffaf83d..2123d1e20 100644 --- a/Projects/Server/Skills.cs +++ b/Projects/Server/Skills.cs @@ -76,6 +76,13 @@ public enum SkillName Throwing = 57 } +public enum Stat +{ + Str, + Dex, + Int +} + [PropertyObject] public class Skill { @@ -135,7 +142,7 @@ public class Skill } } - if (Lock is < SkillLock.Up or > SkillLock.Locked) + if (Lock > SkillLock.Locked) { Lock = SkillLock.Up; } @@ -245,25 +252,29 @@ public class Skill get { var baseValue = Base; - var inv = 100.0 - baseValue; - - if (inv < 0.0) - { - inv = 0.0; - } - - inv /= 100.0; - var statsOffset = Owner.Owner.RawStr * Info.StrScale + Owner.Owner.RawDex * Info.DexScale + Owner.Owner.RawInt * Info.IntScale; - var statTotal = Info.StatTotal * inv; - statsOffset *= inv; + var inv = 100.0 - baseValue; - if (statsOffset > statTotal) + if (inv <= 0.0) { - statsOffset = statTotal; + statsOffset = 0.0; + } + else + { + statsOffset *= inv; + + if (Info.StatTotal > 0) + { + var statTotal = Info.StatTotal * inv; + + if (statsOffset > statTotal) + { + statsOffset = statTotal; + } + } } var value = baseValue + statsOffset; @@ -323,7 +334,7 @@ public class Skill public void SetLockNoRelay(SkillLock skillLock) { - if (skillLock is < SkillLock.Up or > SkillLock.Locked) + if (skillLock > SkillLock.Locked) { return; } @@ -387,7 +398,7 @@ public class SkillInfo public SkillInfo( int skillID, string name, double strScale, double dexScale, double intScale, string title, SkillUseCallback callback, double strGain, double dexGain, double intGain, double gainFactor, - string professionSkillName = null + string professionSkillName, Stat primaryStat, Stat secondaryStat ) { Name = name; @@ -403,6 +414,8 @@ public class SkillInfo GainFactor = gainFactor; ProfessionSkillName = professionSkillName ?? Name.RemoveOrdinal(" "); StatTotal = strScale + dexScale + intScale; + PrimaryStat = primaryStat; + SecondaryStat = secondaryStat; } public SkillUseCallback Callback { get; set; } @@ -431,6 +444,10 @@ public class SkillInfo public string ProfessionSkillName { get; set; } + public Stat PrimaryStat { get; set; } + + public Stat SecondaryStat { get; set; } + public static SkillInfo[] Table { get; set; } = Array.Empty(); } diff --git a/Projects/UOContent/Skills/SkillCheck.cs b/Projects/UOContent/Skills/SkillCheck.cs index a92b151c9..f46fac1aa 100644 --- a/Projects/UOContent/Skills/SkillCheck.cs +++ b/Projects/UOContent/Skills/SkillCheck.cs @@ -1,25 +1,35 @@ using System; +using System.Runtime.CompilerServices; using Server.Factions; using Server.Mobiles; namespace Server.Misc; -// TODO: Make this entirely configurable public static class SkillCheck { - public enum Stat + private static int _statMax; + private static double _statGainChanceMultiplier; + private static bool _usePub45StatGain; + private static double _primaryStatGainChance; + private static TimeSpan _statGainDelay; + private static TimeSpan _petStatGainDelay; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool RollStatIncreaseChance(double statGain) => + statGain / 33.3 * _statGainChanceMultiplier > Utility.RandomDouble(); + + public static void Configure() { - Str, - Dex, - Int + _statMax = ServerConfiguration.GetOrUpdateSetting("stats.statMax", Core.LBR ? 125 : 100); + _statGainChanceMultiplier = ServerConfiguration.GetOrUpdateSetting("stats.gainChanceMultiplier", 1.0); + _primaryStatGainChance = ServerConfiguration.GetSetting("stats.primaryStatGainChance", 0.75); + _statGainDelay = ServerConfiguration.GetSetting("stats.gainDelay", TimeSpan.FromMinutes(Core.ML ? 0.05 : 10)); + _petStatGainDelay = ServerConfiguration.GetSetting("stats.petGainDelay", TimeSpan.FromMinutes(5.0)); + + // Publish 45 - Preparation for UOKR + _usePub45StatGain = ServerConfiguration.GetSetting("stats.usePub45StatGain", Core.ML); } - // Publish 16 changed max stats from 100 to 125 - private static int StatMax = Core.LBR ? 125 : 100; - - private static readonly TimeSpan m_StatGainDelay = TimeSpan.FromMinutes(Core.ML ? 0.05 : 15); - private static readonly TimeSpan m_PetStatGainDelay = TimeSpan.FromMinutes(5.0); - public static void Initialize() { Mobile.SkillCheckLocationHandler = Mobile_SkillCheckLocation; @@ -123,6 +133,12 @@ public static class SkillCheck Gain(from, skill); } } + + // Gain one stat per 10 minutes, regardless of whether you gain in the skill + if (!_usePub45StatGain && success) + { + LegacyGain(from, skill.Info); + } } return success; @@ -220,43 +236,80 @@ public static class SkillCheck if (toLower != skill && toLower.Lock == SkillLock.Down && toLower.BaseFixedPoint >= toGain) { - toLower.BaseFixedPoint -= toGain; + toLower.BaseFixedPoint = Math.Max(toLower.BaseFixedPoint - toGain, 0); break; } } } - if (from is PlayerMobile pm && skill.SkillName == pm.AcceleratedSkill && - pm.AcceleratedStart > Core.Now) + if (from is PlayerMobile pm && skill.SkillName == pm.AcceleratedSkill && pm.AcceleratedStart > Core.Now) { toGain *= Utility.RandomMinMax(2, 5); } - if (!from.Player || skills.Total + toGain <= skills.Cap) + if (!from.Player || skills.Total < skills.Cap) { - skill.BaseFixedPoint += toGain; + skill.BaseFixedPoint = Math.Min(toGain, skill.CapFixedPoint - skill.BaseFixedPoint); } } - if (skill.Lock == SkillLock.Up) + if (_usePub45StatGain && skill.Lock == SkillLock.Up) { var info = skill.Info; - if (from.StrLock == StatLockType.Up && info.StrGain / 33.3 > Utility.RandomDouble()) + var primaryStat = info.PrimaryStat; + var secondaryStat = info.SecondaryStat; + + var primaryStatLock = primaryStat.ToLock(from); + var secondaryStatLock = secondaryStat.ToLock(from); + + if (primaryStatLock != StatLockType.Up && secondaryStatLock != StatLockType.Up) { - GainStat(from, Stat.Str); + return; } - else if (from.DexLock == StatLockType.Up && info.DexGain / 33.3 > Utility.RandomDouble()) + + // Flat 1 in 20 chance to gain anything + if (0.05 * _statGainChanceMultiplier > Utility.RandomDouble()) { - GainStat(from, Stat.Dex); - } - else if (from.IntLock == StatLockType.Up && info.IntGain / 33.3 > Utility.RandomDouble()) - { - GainStat(from, Stat.Int); + // 75% for primary, 25% for secondary - Unless primary is not set to gain. + var statToGain = primaryStatLock is StatLockType.Up && _primaryStatGainChance > Utility.RandomDouble() + ? primaryStat + : secondaryStat; + + GainStat(from, statToGain); } } } + public static void LegacyGain(Mobile from, SkillInfo info) + { + if (info.StrGain > 0 && from.StrLock == StatLockType.Up && RollStatIncreaseChance(info.StrGain)) + { + GainStat(from, Stat.Str); + } + + if (info.DexGain > 0 && from.DexLock == StatLockType.Up && RollStatIncreaseChance(info.DexGain)) + { + GainStat(from, Stat.Dex); + } + + if (info.IntGain > 0 && from.IntLock == StatLockType.Up && RollStatIncreaseChance(info.IntGain)) + { + GainStat(from, Stat.Int); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static StatLockType ToLock(this Stat stat, Mobile from) => + stat switch + { + Stat.Str => from.StrLock, + Stat.Dex => from.DexLock, + Stat.Int => from.IntLock, + _ => StatLockType.Up + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CanLower(Mobile from, Stat stat) { return stat switch @@ -268,6 +321,7 @@ public static class SkillCheck }; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CanRaise(Mobile from, Stat stat) { if (!(from is BaseCreature creature && creature.Controlled)) @@ -280,9 +334,9 @@ public static class SkillCheck return stat switch { - Stat.Str => from.StrLock == StatLockType.Up && from.RawStr < StatMax, - Stat.Dex => from.DexLock == StatLockType.Up && from.RawDex < StatMax, - Stat.Int => from.IntLock == StatLockType.Up && from.RawInt < StatMax, + Stat.Str => from.StrLock == StatLockType.Up && from.RawStr < _statMax, + Stat.Dex => from.DexLock == StatLockType.Up && from.RawDex < _statMax, + Stat.Int => from.IntLock == StatLockType.Up && from.RawInt < _statMax, _ => false }; } @@ -299,17 +353,17 @@ public static class SkillCheck { if (CanLower(from, Stat.Dex) && (from.RawDex < from.RawInt || !CanLower(from, Stat.Int))) { - --from.RawDex; + from.RawDex -= 1; } else if (CanLower(from, Stat.Int)) { - --from.RawInt; + from.RawInt -= 1; } } if (CanRaise(from, Stat.Str)) { - ++from.RawStr; + from.RawStr = Math.Min(from.RawStr + 1, _statMax); } break; @@ -320,17 +374,17 @@ public static class SkillCheck { if (CanLower(from, Stat.Str) && (from.RawStr < from.RawInt || !CanLower(from, Stat.Int))) { - --from.RawStr; + from.RawStr -= 1; } else if (CanLower(from, Stat.Int)) { - --from.RawInt; + from.RawInt -= 1; } } if (CanRaise(from, Stat.Dex)) { - ++from.RawDex; + from.RawDex = Math.Min(from.RawDex + 1, _statMax); } break; @@ -341,17 +395,17 @@ public static class SkillCheck { if (CanLower(from, Stat.Str) && (from.RawStr < from.RawDex || !CanLower(from, Stat.Dex))) { - --from.RawStr; + from.RawStr -= 1; } else if (CanLower(from, Stat.Dex)) { - --from.RawDex; + from.RawDex -= 1; } } if (CanRaise(from, Stat.Int)) { - ++from.RawInt; + from.RawInt = Math.Min(from.RawInt + 1, _statMax); } break; @@ -367,12 +421,12 @@ public static class SkillCheck { if (from is BaseCreature creature && creature.Controlled) { - if (creature.LastStrGain + m_PetStatGainDelay >= Core.Now) + if (creature.LastStrGain + _petStatGainDelay >= Core.Now) { return; } } - else if (from.LastStrGain + m_StatGainDelay >= Core.Now) + else if (from.LastStrGain + _statGainDelay >= Core.Now) { return; } @@ -384,12 +438,12 @@ public static class SkillCheck { if (from is BaseCreature creature && creature.Controlled) { - if (creature.LastDexGain + m_PetStatGainDelay >= Core.Now) + if (creature.LastDexGain + _petStatGainDelay >= Core.Now) { return; } } - else if (from.LastDexGain + m_StatGainDelay >= Core.Now) + else if (from.LastDexGain + _statGainDelay >= Core.Now) { return; } @@ -401,12 +455,12 @@ public static class SkillCheck { if (from is BaseCreature creature && creature.Controlled) { - if (creature.LastIntGain + m_PetStatGainDelay >= Core.Now) + if (creature.LastIntGain + _petStatGainDelay >= Core.Now) { return; } } - else if (from.LastIntGain + m_StatGainDelay >= Core.Now) + else if (from.LastIntGain + _statGainDelay >= Core.Now) { return; }