From ef2717364e7375f9e79276c5a6be80acf6091c65 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Wed, 14 May 2025 01:39:53 -0700 Subject: [PATCH 1/8] Refactor MagerySpell.cs --- Projects/UOContent/Spells/Base/MagerySpell.cs | 75 ++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/Projects/UOContent/Spells/Base/MagerySpell.cs b/Projects/UOContent/Spells/Base/MagerySpell.cs index 71572968e..5d6456557 100644 --- a/Projects/UOContent/Spells/Base/MagerySpell.cs +++ b/Projects/UOContent/Spells/Base/MagerySpell.cs @@ -5,50 +5,42 @@ namespace Server.Spells { public abstract class MagerySpell : Spell { - private static readonly int[] _manaTable = { 4, 6, 9, 11, 14, 20, 40, 50 }; + // Mana costs per spell circle + public static int[] ManaPerCircle { get; set; } = { 4, 6, 9, 11, 14, 20, 40, 50 }; - /* - * Starts at Circle -2 to account for scrolls - * Mana requirements formula: (14 * (circle - 1)) + 2 = 50% probability - * Add or subtract 20 for max or min limits - */ - private static readonly double[] _requiredSkill = Core.ML ? - new[] { -46.0, -32.0, -18.0, -4.0, 10.0, 24.0, 38.0, 52.0, 66.0, 80.0 } : - new[] { -50.0, -30.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 }; + // Minimum skill required per circle (scrolls use Circle+2) + public static double[] RequiredSkillPerCircle { get; set; } = { -50.0, -30.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 }; - public MagerySpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info) - { - } + // Skill check window for casting a spell successfully + public static double SkillCheckWindow { get; set; } = 40.0; + + // Cast delay per tick (seconds) + public static double CastDelaySecondsPerTick { get; set; } = 1.0; + + public MagerySpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info) { } public abstract SpellCircle Circle { get; } - public override TimeSpan CastDelayBase => TimeSpan.FromSeconds((3 + (int)Circle) * CastDelaySecondsPerTick); + public override TimeSpan CastDelayBase => + TimeSpan.FromSeconds((3 + (int)Circle) * CastDelaySecondsPerTick); public override bool ConsumeReagents() => base.ConsumeReagents() || ArcaneGem.ConsumeCharges(Caster, Core.SE ? 1 : 1 + (int)Circle); public override void GetCastSkills(out double min, out double max) { - // Original RunUO algorithm for required skill - // const double chanceOffset = 20.0 - // const double chanceLength = 100.0 / 7.0 - // var avg = chanceLength * circle; - // min = avg - chanceOffset; - // max = avg + chanceOffset; - - // Correct algorithm according to OSI for UOR/UOML - // TODO: Verify this algorithm on OSI for latest expansion. - min = _requiredSkill[(int)(Scroll == null ? Circle + 2 : Circle)]; - max = min + 40; + // Uses scrolls if present, otherwise use spellbook + int skillIndex = (int)(Scroll == null ? Circle + 2 : Circle); + min = RequiredSkillPerCircle[skillIndex]; + max = min + SkillCheckWindow; } - public override int GetMana() => Scroll is BaseWand ? 0 : _manaTable[(int)Circle]; + public override int GetMana() => Scroll is BaseWand ? 0 : ManaPerCircle[(int)Circle]; public override double GetResistSkill(Mobile m) { - var circle = (int)Circle; - - var maxSkill = 1 + circle * 10 + (1 + circle / 6) * 25; + int circle = (int)Circle; + double maxSkill = 1 + circle * 10 + (1 + circle / 6) * 25; if (m.Skills.MagicResist.Value < maxSkill) { @@ -60,40 +52,39 @@ namespace Server.Spells public virtual bool CheckResisted(Mobile target) { - var n = GetResistPercent(target) / 100.0; + double resistChance = GetResistPercent(target) / 100.0; - if (n <= 0.0) + if (resistChance <= 0.0) { return false; } - if (n >= 1.0) + if (resistChance >= 1.0) { return true; } - // Even though this calculation matches AOS+, we don't combine with GetResistSkills because of an assumption - // about how it is used. - var circle = (int)Circle; - var maxSkill = (1 + circle) * 10 + (1 + circle / 6) * 25; + int circle = (int)Circle; + double maxSkill = (1 + circle) * 10 + (1 + circle / 6) * 25; if (target.Skills.MagicResist.Value < maxSkill) { target.CheckSkill(SkillName.MagicResist, 0.0, target.Skills.MagicResist.Cap); } - return n >= Utility.RandomDouble(); + return resistChance >= Utility.RandomDouble(); } public virtual double GetResistPercentForCircle(Mobile target, SpellCircle circle) { - var magicResist = target.Skills.MagicResist.Value; - var firstPercent = magicResist / 5.0; - var secondPercent = magicResist - - ((Caster.Skills[CastSkill].Value - 20.0) / 5.0 + (1 + (int)circle) * 5.0); + double magicResist = target.Skills.MagicResist.Value; + double casterSkill = Caster.Skills[CastSkill].Value; - // Seems should be about half of what stratics says. - return (firstPercent > secondPercent ? firstPercent : secondPercent) / 2.0; + double firstPercent = magicResist / 5.0; + double secondPercent = magicResist - ((casterSkill - 20.0) / 5.0 + (1 + (int)circle) * 5.0); + + // Uses the higher of the two, then halves it + return Math.Max(firstPercent, secondPercent) / 2.0; } public virtual double GetResistPercent(Mobile target) => GetResistPercentForCircle(target, Circle); From b3206d8fa31dd0c1bbaefd657b6632d70ef60620 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Wed, 14 May 2025 01:50:49 -0700 Subject: [PATCH 2/8] Restores ML skill table, clean up --- Projects/UOContent/Spells/Base/MagerySpell.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Projects/UOContent/Spells/Base/MagerySpell.cs b/Projects/UOContent/Spells/Base/MagerySpell.cs index 5d6456557..b160b0575 100644 --- a/Projects/UOContent/Spells/Base/MagerySpell.cs +++ b/Projects/UOContent/Spells/Base/MagerySpell.cs @@ -8,21 +8,24 @@ namespace Server.Spells // Mana costs per spell circle public static int[] ManaPerCircle { get; set; } = { 4, 6, 9, 11, 14, 20, 40, 50 }; - // Minimum skill required per circle (scrolls use Circle+2) - public static double[] RequiredSkillPerCircle { get; set; } = { -50.0, -30.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 }; + // Minimum skill required per circle (scrolls use Circle+2) for non-ML core (10 entries) + public static double[] SkillTable { get; set; } = { -50.0, -30.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 }; + public static double[] SkillTableML { get; set; } = { 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 }; + + public static double[] RequiredSkill => Core.ML ? SkillTableML : SkillTable; // Skill check window for casting a spell successfully public static double SkillCheckWindow { get; set; } = 40.0; // Cast delay per tick (seconds) - public static double CastDelaySecondsPerTick { get; set; } = 1.0; + public static double CastDelay { get; set; } = 1.0; public MagerySpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info) { } public abstract SpellCircle Circle { get; } public override TimeSpan CastDelayBase => - TimeSpan.FromSeconds((3 + (int)Circle) * CastDelaySecondsPerTick); + TimeSpan.FromSeconds((3 + (int)Circle) * CastDelay); public override bool ConsumeReagents() => base.ConsumeReagents() || ArcaneGem.ConsumeCharges(Caster, Core.SE ? 1 : 1 + (int)Circle); @@ -31,7 +34,7 @@ namespace Server.Spells { // Uses scrolls if present, otherwise use spellbook int skillIndex = (int)(Scroll == null ? Circle + 2 : Circle); - min = RequiredSkillPerCircle[skillIndex]; + min = RequiredSkill[skillIndex]; max = min + SkillCheckWindow; } From 77f0ea8773d2b79bad15b0d175e41e5c111f8096 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Wed, 14 May 2025 23:44:25 -0700 Subject: [PATCH 3/8] Apply suggestions from code review --- Projects/UOContent/Spells/Base/MagerySpell.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Projects/UOContent/Spells/Base/MagerySpell.cs b/Projects/UOContent/Spells/Base/MagerySpell.cs index b160b0575..1f3f840d5 100644 --- a/Projects/UOContent/Spells/Base/MagerySpell.cs +++ b/Projects/UOContent/Spells/Base/MagerySpell.cs @@ -33,7 +33,7 @@ namespace Server.Spells public override void GetCastSkills(out double min, out double max) { // Uses scrolls if present, otherwise use spellbook - int skillIndex = (int)(Scroll == null ? Circle + 2 : Circle); + var skillIndex = (int)(Scroll == null ? Circle + 2 : Circle); min = RequiredSkill[skillIndex]; max = min + SkillCheckWindow; } @@ -42,8 +42,8 @@ namespace Server.Spells public override double GetResistSkill(Mobile m) { - int circle = (int)Circle; - double maxSkill = 1 + circle * 10 + (1 + circle / 6) * 25; + var circle = (int)Circle; + var maxSkill = 1 + circle * 10 + (1 + circle / 6) * 25; if (m.Skills.MagicResist.Value < maxSkill) { @@ -55,7 +55,7 @@ namespace Server.Spells public virtual bool CheckResisted(Mobile target) { - double resistChance = GetResistPercent(target) / 100.0; + var resistChance = GetResistPercent(target) / 100.0; if (resistChance <= 0.0) { @@ -67,8 +67,8 @@ namespace Server.Spells return true; } - int circle = (int)Circle; - double maxSkill = (1 + circle) * 10 + (1 + circle / 6) * 25; + var circle = (int)Circle; + var maxSkill = (1 + circle) * 10 + (1 + circle / 6) * 25; if (target.Skills.MagicResist.Value < maxSkill) { @@ -80,11 +80,11 @@ namespace Server.Spells public virtual double GetResistPercentForCircle(Mobile target, SpellCircle circle) { - double magicResist = target.Skills.MagicResist.Value; - double casterSkill = Caster.Skills[CastSkill].Value; + var magicResist = target.Skills.MagicResist.Value; + var casterSkill = Caster.Skills[CastSkill].Value; - double firstPercent = magicResist / 5.0; - double secondPercent = magicResist - ((casterSkill - 20.0) / 5.0 + (1 + (int)circle) * 5.0); + var firstPercent = magicResist / 5.0; + var secondPercent = magicResist - ((casterSkill - 20.0) / 5.0 + (1 + (int)circle) * 5.0); // Uses the higher of the two, then halves it return Math.Max(firstPercent, secondPercent) / 2.0; From 12ca713c33385df15fee8b7cd38e4406828c0555 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 15 May 2025 20:26:58 -0700 Subject: [PATCH 4/8] restores correct ML skill table values --- Projects/UOContent/Spells/Base/MagerySpell.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/UOContent/Spells/Base/MagerySpell.cs b/Projects/UOContent/Spells/Base/MagerySpell.cs index 1f3f840d5..570a81f7f 100644 --- a/Projects/UOContent/Spells/Base/MagerySpell.cs +++ b/Projects/UOContent/Spells/Base/MagerySpell.cs @@ -10,7 +10,7 @@ namespace Server.Spells // Minimum skill required per circle (scrolls use Circle+2) for non-ML core (10 entries) public static double[] SkillTable { get; set; } = { -50.0, -30.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 }; - public static double[] SkillTableML { get; set; } = { 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 }; + public static double[] SkillTableML { get; set; } = { -46.0, -32.0, -18.0, -4.0, 10.0, 24.0, 38.0, 52.0, 66.0, 80.0 }; public static double[] RequiredSkill => Core.ML ? SkillTableML : SkillTable; From f6b4ca81d66368055bd6f639e6d6dc5ebd5426c5 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:48:06 -0700 Subject: [PATCH 5/8] wip, json setup --- Projects/UOContent/Spells/Base/MagerySpell.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Projects/UOContent/Spells/Base/MagerySpell.cs b/Projects/UOContent/Spells/Base/MagerySpell.cs index 570a81f7f..2110816df 100644 --- a/Projects/UOContent/Spells/Base/MagerySpell.cs +++ b/Projects/UOContent/Spells/Base/MagerySpell.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using Server.Items; +using Server.Json; namespace Server.Spells { @@ -106,5 +108,16 @@ namespace Server.Spells return base.GetCastDelay(); } + + private const string _configPath = "Data/magery-spell.json"; + + public static void Configure() + { + var path = Path.Combine(Core.BaseDirectory, _configPath); + if (File.Exists(path)) + { + JsonConfig.Deserialize(path, typeof(MagerySpell)); + } + } } } From f64b49a15502743023c7bc924afa2f53ac046323 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:49:09 -0700 Subject: [PATCH 6/8] wip, json file --- Distribution/Data/magery-spell.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Distribution/Data/magery-spell.json diff --git a/Distribution/Data/magery-spell.json b/Distribution/Data/magery-spell.json new file mode 100644 index 000000000..09c4f35cf --- /dev/null +++ b/Distribution/Data/magery-spell.json @@ -0,0 +1,9 @@ +{ + "MagerySpell": { + "manaPerCircle": [4, 6, 9, 11, 14, 20, 40, 50], + "skillTable": [-50.0, -30.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0], + "skillTableML": [-46.0, -32.0, -18.0, -4.0, 10.0, 24.0, 38.0, 52.0, 66.0, 80.0], + "skillCheckWindow": 40.0, + "castDelay": 1.0 + } +} From 5e53d8116eef4b5c2ef40d73df26a780d83f5487 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Wed, 2 Jul 2025 23:02:13 -0700 Subject: [PATCH 7/8] fixes JsonConfig.Deserialize call --- Projects/UOContent/Spells/Base/MagerySpell.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/UOContent/Spells/Base/MagerySpell.cs b/Projects/UOContent/Spells/Base/MagerySpell.cs index 2110816df..f38f01210 100644 --- a/Projects/UOContent/Spells/Base/MagerySpell.cs +++ b/Projects/UOContent/Spells/Base/MagerySpell.cs @@ -116,7 +116,7 @@ namespace Server.Spells var path = Path.Combine(Core.BaseDirectory, _configPath); if (File.Exists(path)) { - JsonConfig.Deserialize(path, typeof(MagerySpell)); + JsonConfig.Deserialize(path); } } } From a8201c93b0bfe775a07670c16ba97a6e5cd31851 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Wed, 2 Jul 2025 23:52:26 -0700 Subject: [PATCH 8/8] crash fix --- Projects/UOContent/Spells/Base/MagerySpell.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Projects/UOContent/Spells/Base/MagerySpell.cs b/Projects/UOContent/Spells/Base/MagerySpell.cs index f38f01210..5766f7113 100644 --- a/Projects/UOContent/Spells/Base/MagerySpell.cs +++ b/Projects/UOContent/Spells/Base/MagerySpell.cs @@ -109,14 +109,30 @@ namespace Server.Spells return base.GetCastDelay(); } + public class MagerySpellConfig + { + public int[] ManaPerCircle { get; set; } + public double[] SkillTable { get; set; } + public double[] SkillTableML { get; set; } + public double? SkillCheckWindow { get; set; } + public double? CastDelay { get; set; } + } + private const string _configPath = "Data/magery-spell.json"; public static void Configure() { var path = Path.Combine(Core.BaseDirectory, _configPath); + if (File.Exists(path)) { - JsonConfig.Deserialize(path); + var config = JsonConfig.Deserialize(path); + + if (config.ManaPerCircle != null) { ManaPerCircle = config.ManaPerCircle; } + if (config.SkillTable != null) { SkillTable = config.SkillTable; } + if (config.SkillTableML != null) { SkillTableML = config.SkillTableML; } + if (config.SkillCheckWindow.HasValue) { SkillCheckWindow = config.SkillCheckWindow.Value; } + if (config.CastDelay.HasValue) { CastDelay = config.CastDelay.Value; } } } }