From 158e7f2e5ff539a9cf41c80c1effa17da1f1ddc5 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:58:29 -0700 Subject: [PATCH] feat(throwing): add Ter Mur reptiles (Raptor + slith family) and their SA claws (#2515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the gargoyle Throwing work (#2510/#2512/#2514). Ports two Stygian Abyss creatures from ServUO so two of the throwing artifacts finally have a real OSI drop source instead of being `[add`-only. ## Changes - **`Raptor`** and **`StoneSlith`** — ported stat-for-stat from ServUO. They **spawn automatically**: `Distribution/Data/Spawns/post-uoml/termur/TerMur.json` already contained Raptor/StoneSlith spawner entries referencing these class names, so no spawn-file edits were needed. - **`RaptorClaw`** (Boomerang-based) and **`StoneSlithClaw`** (Cyclone-based) artifacts, dropped from each creature's `OnDeath` at ServUO's 0.5% rate (uncontrolled only). - StoneSlith retains its `GraspingClaw` monster ability; both use `BleedAttack`. ## ModernUO idioms Default range perception (16) / fight range, derived `GetSpeeds` (no `SetSpeed`), `[SerializationGenerator(0)]` codegen, collection-expression arrays. ## Documented omissions vs ServUO Flagged as TODO in-code — all are content missing from ModernUO, not silent drops: Raptor's friend-spawn timer + its 25% `AncientPotteryFragments` drop; StoneSlith's `TailSwipe`, `DragonBlood`, and `SlithEye`/`TatteredAncientScroll`/`AncientPotteryFragments` drops; plus `HideType.Horned/Spined` and `PackInstinct.Ostard` (no ModernUO equivalents yet). ## Not included The other three throwing artifacts (Storm Caller, Banshee's Call, Wind of Corruption) drop from renowned/boss creatures via a `BaseRenowned` artifact-list system that doesn't exist in ModernUO yet — a separate, larger effort. AbyssReaver stays with the (deferred) Into-the-Void quest. Verified: server builds clean; throwing suite 14/14. --- .../UOContent/Items/Weapons/SlayerGroup.cs | 4 + .../Items/Weapons/Throwing/RaptorClaw.cs | 23 ++++++ .../Items/Weapons/Throwing/StoneSlithClaw.cs | 23 ++++++ .../Server.Items.RaptorClaw.v0.json | 4 + .../Server.Items.StoneSlithClaw.v0.json | 4 + .../Migrations/Server.Mobiles.Raptor.v0.json | 4 + .../Migrations/Server.Mobiles.Slith.v0.json | 4 + .../Server.Mobiles.StoneSlith.v0.json | 4 + .../Server.Mobiles.ToxicSlith.v0.json | 4 + .../Mobiles/Abilities/MonsterAbilities.cs | 1 + .../Mobiles/Abilities/MonsterAbilityType.cs | 3 +- .../UOContent/Mobiles/Abilities/TailSwipe.cs | 35 +++++++++ .../Mobiles/Monsters/Reptile/Raptor.cs | 76 +++++++++++++++++++ .../Mobiles/Monsters/Reptile/Slith.cs | 56 ++++++++++++++ .../Mobiles/Monsters/Reptile/StoneSlith.cs | 72 ++++++++++++++++++ .../Mobiles/Monsters/Reptile/ToxicSlith.cs | 56 ++++++++++++++ 16 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 Projects/UOContent/Items/Weapons/Throwing/RaptorClaw.cs create mode 100644 Projects/UOContent/Items/Weapons/Throwing/StoneSlithClaw.cs create mode 100644 Projects/UOContent/Migrations/Server.Items.RaptorClaw.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Items.StoneSlithClaw.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.Raptor.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.Slith.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.StoneSlith.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.ToxicSlith.v0.json create mode 100644 Projects/UOContent/Mobiles/Abilities/TailSwipe.cs create mode 100644 Projects/UOContent/Mobiles/Monsters/Reptile/Raptor.cs create mode 100644 Projects/UOContent/Mobiles/Monsters/Reptile/Slith.cs create mode 100644 Projects/UOContent/Mobiles/Monsters/Reptile/StoneSlith.cs create mode 100644 Projects/UOContent/Mobiles/Monsters/Reptile/ToxicSlith.cs diff --git a/Projects/UOContent/Items/Weapons/SlayerGroup.cs b/Projects/UOContent/Items/Weapons/SlayerGroup.cs index b358431d0..87380978a 100644 --- a/Projects/UOContent/Items/Weapons/SlayerGroup.cs +++ b/Projects/UOContent/Items/Weapons/SlayerGroup.cs @@ -342,6 +342,7 @@ namespace Server.Items typeof(OphidianMage), typeof(OphidianMatriarch), typeof(OphidianWarrior), + typeof(Raptor), typeof(Reptalon), typeof(SeaSerpent), typeof(Serado), @@ -350,6 +351,9 @@ namespace Server.Items typeof(SilverSerpent), typeof(SkeletalDragon), typeof(Snake), + typeof(Slith), + typeof(StoneSlith), + typeof(ToxicSlith), typeof(SwampDragon), typeof(WhiteWyrm), typeof(Wyvern), diff --git a/Projects/UOContent/Items/Weapons/Throwing/RaptorClaw.cs b/Projects/UOContent/Items/Weapons/Throwing/RaptorClaw.cs new file mode 100644 index 000000000..9b3ed6a10 --- /dev/null +++ b/Projects/UOContent/Items/Weapons/Throwing/RaptorClaw.cs @@ -0,0 +1,23 @@ +using ModernUO.Serialization; + +namespace Server.Items; + +[SerializationGenerator(0)] +public partial class RaptorClaw : Boomerang +{ + [Constructible] + public RaptorClaw() + { + Hue = 53; + Attributes.AttackChance = 12; + Attributes.WeaponSpeed = 30; + Attributes.WeaponDamage = 35; + Slayer = SlayerName.Silver; + WeaponAttributes.HitLeechStam = 40; + } + + public override int LabelNumber => 1112394; // Raptor Claw + + public override int InitMinHits => 255; + public override int InitMaxHits => 255; +} diff --git a/Projects/UOContent/Items/Weapons/Throwing/StoneSlithClaw.cs b/Projects/UOContent/Items/Weapons/Throwing/StoneSlithClaw.cs new file mode 100644 index 000000000..028253e0a --- /dev/null +++ b/Projects/UOContent/Items/Weapons/Throwing/StoneSlithClaw.cs @@ -0,0 +1,23 @@ +using ModernUO.Serialization; + +namespace Server.Items; + +[SerializationGenerator(0)] +public partial class StoneSlithClaw : Cyclone +{ + [Constructible] + public StoneSlithClaw() + { + Hue = 1150; + Attributes.WeaponSpeed = 25; + Attributes.WeaponDamage = 45; + Slayer = SlayerName.DaemonDismissal; + WeaponAttributes.HitHarm = 40; + WeaponAttributes.HitLowerDefend = 40; + } + + public override int LabelNumber => 1112393; // Stone Slith Claw + + public override int InitMinHits => 255; + public override int InitMaxHits => 255; +} diff --git a/Projects/UOContent/Migrations/Server.Items.RaptorClaw.v0.json b/Projects/UOContent/Migrations/Server.Items.RaptorClaw.v0.json new file mode 100644 index 000000000..8165371d4 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.RaptorClaw.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Items.RaptorClaw" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.StoneSlithClaw.v0.json b/Projects/UOContent/Migrations/Server.Items.StoneSlithClaw.v0.json new file mode 100644 index 000000000..f01d64da8 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.StoneSlithClaw.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Items.StoneSlithClaw" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Raptor.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Raptor.v0.json new file mode 100644 index 000000000..4e2bcf4ab --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Raptor.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Raptor" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Slith.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Slith.v0.json new file mode 100644 index 000000000..1254f919a --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Slith.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Slith" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.StoneSlith.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.StoneSlith.v0.json new file mode 100644 index 000000000..58b5c4b1c --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.StoneSlith.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.StoneSlith" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.ToxicSlith.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.ToxicSlith.v0.json new file mode 100644 index 000000000..1184eca95 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.ToxicSlith.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.ToxicSlith" +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs index 652bc5b05..0afdf05de 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs @@ -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); diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs index 9f188243d..fe0fe46f4 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs @@ -20,5 +20,6 @@ public enum MonsterAbilityType FanningFire, // Fire debuff RuneCorruption, FanThrow, - BloodBath + BloodBath, + TailSwipe } diff --git a/Projects/UOContent/Mobiles/Abilities/TailSwipe.cs b/Projects/UOContent/Mobiles/Abilities/TailSwipe.cs new file mode 100644 index 000000000..9982355b2 --- /dev/null +++ b/Projects/UOContent/Mobiles/Abilities/TailSwipe.cs @@ -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)); + } + } +} diff --git a/Projects/UOContent/Mobiles/Monsters/Reptile/Raptor.cs b/Projects/UOContent/Mobiles/Monsters/Reptile/Raptor.cs new file mode 100644 index 000000000..36c3088bb --- /dev/null +++ b/Projects/UOContent/Mobiles/Monsters/Reptile/Raptor.cs @@ -0,0 +1,76 @@ +using ModernUO.Serialization; +using Server.Items; + +namespace Server.Mobiles; + +[SerializationGenerator(0)] +public partial class Raptor : BaseCreature +{ + [Constructible] + public Raptor() : base(AIType.AI_Melee) + { + Body = 730; + + SetStr(404, 471); + SetDex(132, 155); + SetInt(105, 145); + + SetHits(343, 400); + + SetDamage(11, 17); + + SetDamageType(ResistanceType.Physical, 100); + + SetResistance(ResistanceType.Physical, 45, 50); + SetResistance(ResistanceType.Fire, 50, 60); + SetResistance(ResistanceType.Cold, 40, 50); + SetResistance(ResistanceType.Poison, 20, 30); + SetResistance(ResistanceType.Energy, 30, 40); + + SetSkill(SkillName.MagicResist, 75.1, 90.0); + SetSkill(SkillName.Tactics, 75.1, 100.0); + SetSkill(SkillName.Wrestling, 70.1, 95.1); + + Fame = 7500; + Karma = -7500; + + Tamable = true; + MinTameSkill = 107.1; + ControlSlots = 2; + + // TODO(SA-creatures): ServUO Raptors summon up to 2 "friend" raptors on entering combat + // (InternalTimer/CheckFriends). Omitted pending a ModernUO timer/MonsterAbility adaptation. + // Also unported vs ServUO: the 25% AncientPotteryFragments corpse drop (item class not yet in ModernUO). + } + + public override string CorpseName => "a raptor corpse"; + public override string DefaultName => "a raptor"; + + public override int TreasureMapLevel => 3; + public override int Meat => 7; + public override int Hides => 11; + public override HideType HideType => HideType.Horned; + public override PackInstinct PackInstinct => PackInstinct.Ostard; + + public override WeaponAbility GetWeaponAbility() => WeaponAbility.BleedAttack; + + public override int GetIdleSound() => 1573; + public override int GetAngerSound() => 1570; + public override int GetHurtSound() => 1572; + public override int GetDeathSound() => 1571; + + public override void GenerateLoot() + { + AddLoot(LootPack.Rich, 2); + } + + public override void OnDeath(Container c) + { + base.OnDeath(c); + + if (!Controlled && Utility.RandomDouble() <= 0.005) + { + c.DropItem(new RaptorClaw()); + } + } +} diff --git a/Projects/UOContent/Mobiles/Monsters/Reptile/Slith.cs b/Projects/UOContent/Mobiles/Monsters/Reptile/Slith.cs new file mode 100644 index 000000000..90178101e --- /dev/null +++ b/Projects/UOContent/Mobiles/Monsters/Reptile/Slith.cs @@ -0,0 +1,56 @@ +using ModernUO.Serialization; + +namespace Server.Mobiles; + +[SerializationGenerator(0)] +public partial class Slith : BaseCreature +{ + // ServUO SpecialAbility.DragonBreath -> closest ModernUO monster ability. + private static readonly MonsterAbility[] _abilities = [MonsterAbilities.FireBreath]; + + [Constructible] + public Slith() : base(AIType.AI_Melee) + { + Body = 734; + + SetStr(129, 136); + SetDex(72, 75); + SetInt(12, 13); + + SetHits(84, 85); + + SetDamage(6, 24); + + SetDamageType(ResistanceType.Physical, 100); + + SetResistance(ResistanceType.Physical, 35, 40); + SetResistance(ResistanceType.Fire, 35, 45); + SetResistance(ResistanceType.Poison, 25, 35); + SetResistance(ResistanceType.Energy, 25, 30); + + SetSkill(SkillName.MagicResist, 59.1, 63.5); + SetSkill(SkillName.Tactics, 74.6, 76.4); + SetSkill(SkillName.Wrestling, 62.0, 77.1); + + Tamable = true; + ControlSlots = 1; + MinTameSkill = 80.7; + + // TODO(SA-creatures): unported vs ServUO — DragonBlood carving and the minor drops + // SlithEye / AncientPotteryFragments / TatteredAncientScroll (item classes not yet in ModernUO). + } + + public override string CorpseName => "a slith corpse"; + public override string DefaultName => "a slith"; + + public override int TreasureMapLevel => 2; + public override int Meat => 6; + public override int Hides => 10; + + public override MonsterAbility[] GetMonsterAbilities() => _abilities; + + public override void GenerateLoot() + { + AddLoot(LootPack.Average, 2); + } +} diff --git a/Projects/UOContent/Mobiles/Monsters/Reptile/StoneSlith.cs b/Projects/UOContent/Mobiles/Monsters/Reptile/StoneSlith.cs new file mode 100644 index 000000000..c7a3d7a45 --- /dev/null +++ b/Projects/UOContent/Mobiles/Monsters/Reptile/StoneSlith.cs @@ -0,0 +1,72 @@ +using ModernUO.Serialization; +using Server.Items; + +namespace Server.Mobiles; + +[SerializationGenerator(0)] +public partial class StoneSlith : BaseCreature +{ + private static readonly MonsterAbility[] _abilities = [MonsterAbilities.GraspingClaw, MonsterAbilities.TailSwipe]; + + [Constructible] + public StoneSlith() : base(AIType.AI_Melee) + { + Body = 734; + + SetStr(250, 300); + SetDex(76, 90); + SetInt(34, 69); + + SetHits(154, 166); + SetStam(76, 90); + SetMana(34, 69); + + SetDamage(6, 24); + + SetDamageType(ResistanceType.Physical, 100); + + SetResistance(ResistanceType.Physical, 50, 55); + SetResistance(ResistanceType.Fire, 20, 30); + SetResistance(ResistanceType.Cold, 10, 20); + SetResistance(ResistanceType.Poison, 30, 40); + SetResistance(ResistanceType.Energy, 30, 40); + + SetSkill(SkillName.MagicResist, 86.8, 95.1); + SetSkill(SkillName.Tactics, 82.6, 88.6); + SetSkill(SkillName.Wrestling, 75.8, 87.4); + SetSkill(SkillName.Anatomy, 0.0, 2.9); + + Tamable = true; + MinTameSkill = 65.1; + ControlSlots = 2; + + // 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"; + public override string DefaultName => "a stone slith"; + + public override int Meat => 1; + public override int Hides => 12; + public override HideType HideType => HideType.Spined; + + public override WeaponAbility GetWeaponAbility() => WeaponAbility.BleedAttack; + + public override MonsterAbility[] GetMonsterAbilities() => _abilities; + + public override void GenerateLoot() + { + AddLoot(LootPack.Average, 2); + } + + public override void OnDeath(Container c) + { + base.OnDeath(c); + + if (!Controlled && Utility.RandomDouble() <= 0.005) + { + c.DropItem(new StoneSlithClaw()); + } + } +} diff --git a/Projects/UOContent/Mobiles/Monsters/Reptile/ToxicSlith.cs b/Projects/UOContent/Mobiles/Monsters/Reptile/ToxicSlith.cs new file mode 100644 index 000000000..fa33e4cb2 --- /dev/null +++ b/Projects/UOContent/Mobiles/Monsters/Reptile/ToxicSlith.cs @@ -0,0 +1,56 @@ +using ModernUO.Serialization; + +namespace Server.Mobiles; + +[SerializationGenerator(0)] +public partial class ToxicSlith : BaseCreature +{ + // ServUO SpecialAbility.DragonBreath -> closest ModernUO monster ability. + private static readonly MonsterAbility[] _abilities = [MonsterAbilities.FireBreath]; + + [Constructible] + public ToxicSlith() : base(AIType.AI_Melee) + { + Body = 734; + Hue = 476; + + SetStr(223, 306); + SetDex(231, 258); + SetInt(30, 35); + + SetHits(197, 215); + SetStam(231, 258); + + SetDamage(6, 24); + + SetDamageType(ResistanceType.Physical, 100); + + SetResistance(ResistanceType.Physical, 35, 45); + SetResistance(ResistanceType.Fire, 0, 9); + SetResistance(ResistanceType.Cold, 5, 10); + SetResistance(ResistanceType.Poison, 100, 100); + SetResistance(ResistanceType.Energy, 5, 7); + + SetSkill(SkillName.MagicResist, 95.4, 98.3); + SetSkill(SkillName.Tactics, 85.5, 90.9); + SetSkill(SkillName.Wrestling, 90.4, 95.1); + SetSkill(SkillName.Poisoning, 90.0, 110.0); + + // TODO(SA-creatures): unported vs ServUO — DragonBlood carving and the minor drops + // ToxicVenomSac / SlithEye / AncientPotteryFragments / TatteredAncientScroll (not yet in ModernUO). + } + + public override string CorpseName => "a slith corpse"; + public override string DefaultName => "a toxic slith"; + + public override int Meat => 6; + public override int Hides => 11; + public override HideType HideType => HideType.Horned; + + public override MonsterAbility[] GetMonsterAbilities() => _abilities; + + public override void GenerateLoot() + { + AddLoot(LootPack.Average, 2); + } +}