From a628d30ed6ff5570b7286294721bc437055b42e6 Mon Sep 17 00:00:00 2001 From: WarrentyExpired Date: Thu, 1 Oct 2026 13:45:10 -0400 Subject: [PATCH] #W# Work: Working on innate abilities. --- Distribution/Configuration/modernuo.json | 3 +- .../Engines/InnateWeaponAbilities.cs | 97 ++++++++++++++++++- .../Engines/InnateWeaponAbilities.cs.oldStyle | 84 ++++++++++++++++ 3 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 Projects/UOContent/Engines/InnateWeaponAbilities.cs.oldStyle diff --git a/Distribution/Configuration/modernuo.json b/Distribution/Configuration/modernuo.json index 8170db7d7..3bd942854 100644 --- a/Distribution/Configuration/modernuo.json +++ b/Distribution/Configuration/modernuo.json @@ -113,6 +113,7 @@ "exileBestiary.damage.markTickSeconds": "00:00:02", "exileBestiary.damage.markTickPercent": "0.01", "exileBestiary.drop.chance": "-1.0", - "exileBestiary.drop.pityThreshold": "40" + "exileBestiary.drop.pityThreshold": "40", + "visibleDamage": "True" } } diff --git a/Projects/UOContent/Engines/InnateWeaponAbilities.cs b/Projects/UOContent/Engines/InnateWeaponAbilities.cs index 57471ff49..ef556541d 100644 --- a/Projects/UOContent/Engines/InnateWeaponAbilities.cs +++ b/Projects/UOContent/Engines/InnateWeaponAbilities.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using Server.Mobiles; using Server.Items; @@ -6,36 +7,67 @@ namespace Server.Combat { public static class InnateWeaponAbilities { + // Upgraded to a ConcurrentDictionary to handle ModernUO's multithreaded combat + // Using 'long' to track Core.TickCount instead of DateTime + private static readonly ConcurrentDictionary _cooldowns = new ConcurrentDictionary(); + public static void CheckProc(Mobile attacker, Mobile defender, BaseWeapon weapon) { if (attacker == null || defender == null || weapon == null || !defender.Alive) return; - double procChance = 0.10; + // 1. Thread-safe Cooldown Check + if (_cooldowns.TryGetValue(attacker, out long nextAllowedProc)) + { + // If the current server tick is less than the allowed tick, they are still on cooldown + if (Core.TickCount < nextAllowedProc) + return; + } + + double wpnSkill = attacker.Skills[weapon.Skill].Base; + double tactics = attacker.Skills[SkillName.Tactics].Base; + + if (wpnSkill < 69.9 || tactics < 69.9) + return; + + // Ensure you drop this back down to 0.10 if you raised it for testing! + double procChance = 1.0; if (Utility.RandomDouble() >= procChance) return; + // 2. Proc Successful: Lock them out for 5000 milliseconds (5 seconds) + _cooldowns[attacker] = Core.TickCount + 5000; + + bool isGrandmaster = (wpnSkill >= 99.9 && tactics >= 99.9); + bool useTierTwo = isGrandmaster && Utility.RandomBool(); + switch (weapon.Skill) { case SkillName.Swords: - ApplyBleed(attacker, defender); + if (useTierTwo) ApplySever(attacker, defender); + else ApplyBleed(attacker, defender); break; case SkillName.Archery: - ApplyHinder(attacker, defender); + if (useTierTwo) ApplyHeadshot(attacker, defender); + else ApplyHinder(attacker, defender); break; case SkillName.Fencing: - ApplyPierce(attacker, defender); + if (useTierTwo) ApplyQuickStrike(attacker, defender); + else ApplyPierce(attacker, defender); break; case SkillName.Macing: - ApplyConcussion(attacker, defender); + if (useTierTwo) ApplyShatter(attacker, defender); + else ApplyConcussion(attacker, defender); break; } } + // --- TIER 1 ABILITIES --- + private static void ApplyBleed(Mobile attacker, Mobile defender) { defender.PlaySound(0x133); @@ -45,6 +77,7 @@ namespace Server.Combat BleedAttack.BeginBleed(defender, attacker); } + // Note: If you see double messages here, it's because BeginBleed() natively sends its own message too! attacker.SendMessage(0x3F, "Your blade inflicts a bleeding wound!"); defender.SendMessage(0x22, "You are bleeding severely!"); } @@ -80,5 +113,59 @@ namespace Server.Combat attacker.SendMessage(0x35, $"Your crushing blow drained {manaDrain} mana and {stamDrain} stamina!"); } + + // --- TIER 2 ABILITIES --- + + private static void ApplySever(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x23B); + defender.FixedParticles(0x3779, 1, 15, 9905, 32, 7, EffectLayer.Head); + + // Heavy physical burst damage + int damage = Utility.RandomMinMax(15, 25); + AOS.Damage(defender, attacker, damage, 100, 0, 0, 0, 0); + + attacker.SendMessage(0x21, $"You brutally sever your opponent, dealing {damage} extra damage!"); + defender.SendMessage(0x22, "A brutal strike tears into your flesh!"); + } + + private static void ApplyHeadshot(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x11C); // Heavy thud + defender.FixedParticles(0x373A, 1, 15, 9905, 33, 7, EffectLayer.Head); + + // Extreme armor-ignoring damage + int directDamage = Utility.RandomMinMax(20, 30); + AOS.Damage(defender, attacker, directDamage, true, 100, 0, 0, 0, 0, 0); + + attacker.SendMessage(0x481, $"HEADSHOT! You inflict {directDamage} direct damage!"); + defender.SendMessage(0x22, "An arrow strikes your head, bypassing your armor entirely!"); + } + + private static void ApplyQuickStrike(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x2B1); // Fast swish sound + defender.FixedParticles(0x37B9, 1, 14, 9905, 32, 7, EffectLayer.Waist); + + // Instantly resets the weapon swing timer so the next attack happens immediately + attacker.NextCombatTime = Core.TickCount; + + attacker.SendMessage(0x42, "You strike with blinding speed, granting an immediate follow-up attack!"); + defender.SendMessage(0x22, "Your opponent strikes with blinding speed!"); + } + + private static void ApplyShatter(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x308); // Breaking sound + defender.FixedParticles(0x375A, 1, 15, 9905, 12, 7, EffectLayer.Waist); + + // Halves current stamina and deals moderate crushing damage + defender.Stam /= 2; + int damage = Utility.RandomMinMax(10, 20); + AOS.Damage(defender, attacker, damage, 100, 0, 0, 0, 0); + + attacker.SendMessage(0x35, "You shatter your opponent's guard, decimating their stamina!"); + defender.SendMessage(0x22, "A shattering blow knocks the wind out of you!"); + } } } diff --git a/Projects/UOContent/Engines/InnateWeaponAbilities.cs.oldStyle b/Projects/UOContent/Engines/InnateWeaponAbilities.cs.oldStyle new file mode 100644 index 000000000..57471ff49 --- /dev/null +++ b/Projects/UOContent/Engines/InnateWeaponAbilities.cs.oldStyle @@ -0,0 +1,84 @@ +using System; +using Server.Mobiles; +using Server.Items; + +namespace Server.Combat +{ + public static class InnateWeaponAbilities + { + public static void CheckProc(Mobile attacker, Mobile defender, BaseWeapon weapon) + { + if (attacker == null || defender == null || weapon == null || !defender.Alive) + return; + + double procChance = 0.10; + + if (Utility.RandomDouble() >= procChance) + return; + + switch (weapon.Skill) + { + case SkillName.Swords: + ApplyBleed(attacker, defender); + break; + + case SkillName.Archery: + ApplyHinder(attacker, defender); + break; + + case SkillName.Fencing: + ApplyPierce(attacker, defender); + break; + + case SkillName.Macing: + ApplyConcussion(attacker, defender); + break; + } + } + + private static void ApplyBleed(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x133); + + if (typeof(BaseWeapon).Assembly.GetType("Server.Items.BleedAttack") != null) + { + BleedAttack.BeginBleed(defender, attacker); + } + + attacker.SendMessage(0x3F, "Your blade inflicts a bleeding wound!"); + defender.SendMessage(0x22, "You are bleeding severely!"); + } + + private static void ApplyHinder(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x204); + defender.FixedEffect(0x376A, 9, 32); + + defender.Freeze(TimeSpan.FromSeconds(3.0)); + + attacker.SendMessage(0x481, "Your arrow pins the target in place!"); + defender.SendMessage(0x22, "You have been pinned and cannot move!"); + } + + private static void ApplyPierce(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x56); + int directDamage = (int)(attacker.Skills[SkillName.Tactics].Value * 0.2) + Utility.RandomMinMax(2, 5); + AOS.Damage(defender, attacker, directDamage, true, 100, 0, 0, 0, 0, 0); + attacker.SendMessage(0x42, $"Your precise thrust pierces through their armor for {directDamage} direct damage!"); + defender.SendMessage(0x22, $"You are pierced for {directDamage} direct damage, bypassing your armor!"); + } + + private static void ApplyConcussion(Mobile attacker, Mobile defender) + { + defender.PlaySound(0x213); + + int manaDrain = Utility.RandomMinMax(10, 15); + int stamDrain = Utility.RandomMinMax(10, 15); + defender.Mana -= manaDrain; + defender.Stam -= stamDrain; + + attacker.SendMessage(0x35, $"Your crushing blow drained {manaDrain} mana and {stamDrain} stamina!"); + } + } +}