feat: add TailSwipe monster ability and give it to StoneSlith

Port StoneSlith's TailSwipe from ServUO as a proper ModernUO MonsterAbility
(MonsterAbilitySingleTarget, on GiveMeleeDamage, 30s cooldown like ServUO's
SpecialAbility base). On hit it plays the tail-swipe effect and either stuns
(3s paralyze) or confuses (-20 Dex/Int for 5s).

Uses the OSI/Stratics-reported ~90% stun / ~10% confuse split rather than
ServUO's incorrect even 50/50. StoneSlith now runs GraspingClaw + TailSwipe,
closing the last ability gap from the port.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-07-02 23:52:58 -07:00
parent e4f61096ae
commit 34c085a777
4 changed files with 41 additions and 5 deletions

View file

@ -13,6 +13,7 @@ public static class MonsterAbilities
// Resistance Debuffs
public static GraspingClaw GraspingClaw => new();
public static TailSwipe TailSwipe => new();
public static RuneCorruption RuneCorruption => new();
public static FanningFire FanningFire => new(0.05, -10, 35, 45);

View file

@ -20,5 +20,6 @@ public enum MonsterAbilityType
FanningFire, // Fire debuff
RuneCorruption,
FanThrow,
BloodBath
BloodBath,
TailSwipe
}

View file

@ -0,0 +1,35 @@
using System;
namespace Server.Mobiles;
public class TailSwipe : MonsterAbilitySingleTarget
{
public override MonsterAbilityType AbilityType => MonsterAbilityType.TailSwipe;
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveMeleeDamage;
public override double ChanceToTrigger => 0.3;
// ServUO's SpecialAbility base cooldown; TailSwipe does not override it.
public override TimeSpan MinTriggerCooldown => TimeSpan.FromSeconds(30);
public override TimeSpan MaxTriggerCooldown => TimeSpan.FromSeconds(30);
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
source.DoHarmful(defender);
defender.PlaySound(0x204);
defender.FixedEffect(0x376A, 6, 1);
// OSI/Stratics reports roughly a 90% stun / 10% confuse split (ServUO's even 50/50 is incorrect).
if (Utility.RandomDouble() < 0.10)
{
defender.SendLocalizedMessage(1112555); // You're left confused as the creature's tail catches you right in the face!
defender.AddStatMod(new StatMod(StatType.Dex, "[TailSwipe] Dex", -20, TimeSpan.FromSeconds(5.0)));
defender.AddStatMod(new StatMod(StatType.Int, "[TailSwipe] Int", -20, TimeSpan.FromSeconds(5.0)));
}
else
{
defender.SendLocalizedMessage(1112554); // You're stunned as the creature's tail knocks the wind out of you.
defender.Paralyze(TimeSpan.FromSeconds(3.0));
}
}
}

View file

@ -6,7 +6,7 @@ namespace Server.Mobiles;
[SerializationGenerator(0)]
public partial class StoneSlith : BaseCreature
{
private static readonly MonsterAbility[] _abilities = [MonsterAbilities.GraspingClaw];
private static readonly MonsterAbility[] _abilities = [MonsterAbilities.GraspingClaw, MonsterAbilities.TailSwipe];
[Constructible]
public StoneSlith() : base(AIType.AI_Melee)
@ -40,9 +40,8 @@ public partial class StoneSlith : BaseCreature
MinTameSkill = 65.1;
ControlSlots = 2;
// TODO(SA-creatures): unported vs ServUO — SpecialAbility.TailSwipe (no ModernUO equivalent),
// DragonBlood carving, and the minor drops SlithEye / TatteredAncientScroll / AncientPotteryFragments
// (item classes not yet in ModernUO).
// TODO(SA-creatures): unported vs ServUO — DragonBlood carving, and the minor drops
// SlithEye / TatteredAncientScroll / AncientPotteryFragments (item classes not yet in ModernUO).
}
public override string CorpseName => "a slith corpse";