diff --git a/Distribution/Data/MLQuests.cfg b/Distribution/Data/MLQuests.cfg index 76be8b793..ef94ed9de 100644 --- a/Distribution/Data/MLQuests.cfg +++ b/Distribution/Data/MLQuests.cfg @@ -305,3 +305,6 @@ PointyEars Gorrow Drithen Sledge # Lost items LostAndFound BatteredBucket + +# Stygian Abyss +IntoTheVoidQuest Agralem diff --git a/Projects/UOContent/Engines/ML Quests/Definitions/IntoTheVoid.cs b/Projects/UOContent/Engines/ML Quests/Definitions/IntoTheVoid.cs new file mode 100644 index 000000000..9eaa2b5a6 --- /dev/null +++ b/Projects/UOContent/Engines/ML Quests/Definitions/IntoTheVoid.cs @@ -0,0 +1,70 @@ +using ModernUO.Serialization; +using Server.Engines.MLQuests.Objectives; +using Server.Engines.MLQuests.Rewards; +using Server.Items; +using Server.Mobiles; + +namespace Server.Engines.MLQuests.Definitions; + +public class IntoTheVoidQuest : MLQuest +{ + public IntoTheVoidQuest() + { + Activated = true; + OneTimeOnly = true; + Title = 1112687; // Into the Void + Description = 1112690; // Quest description cliloc + RefusalMessage = 1112691; + InProgressMessage = 1112692; + CompletionMessage = 1112693; + + // TODO: ServUO targets BaseVoidCreature (a broader set of SA void creatures). + // Expand the target list when additional void creature types are implemented. + Objectives.Add(new KillObjective(10, new[] { typeof(WandererOfTheVoid) }, "Void Daemons")); + + Rewards.Add(new ItemReward(1112694, typeof(AbyssReaver))); // Abyss Reaver + } +} + +[QuesterName("Agralem")] +[SerializationGenerator(0, false)] +public partial class Agralem : BaseCreature +{ + [Constructible] + public Agralem() : base(AIType.AI_Vendor, FightMode.None, 2) + { + Title = "the Bladeweaver"; + + Race = Race.Gargoyle; + Body = 666; + Female = false; + Hue = 34536; + + HairItemID = 0x425D; + HairHue = 0x31D; + + CantWalk = true; + + InitStats(100, 100, 25); + + SetSkill(SkillName.Anatomy, 65.0, 90.0); + SetSkill(SkillName.MagicResist, 65.0, 90.0); + SetSkill(SkillName.Tactics, 65.0, 90.0); + SetSkill(SkillName.Throwing, 65.0, 90.0); + + AddItem(new Cyclone { LootType = LootType.Blessed }); + AddItem(new GargishLeatherKiltType1 { Hue = 2305, LootType = LootType.Blessed }); + AddItem(new GargishLeatherChestType1 { Hue = 2305, LootType = LootType.Blessed }); + AddItem(new GargishLeatherArmsType1 { Hue = 2305, LootType = LootType.Blessed }); + } + + public override bool IsInvulnerable => true; + public override string DefaultName => "Agralem"; + + public override bool CanShout => true; + + public override void Shout(PlayerMobile pm) + { + MLQuestSystem.Tell(this, pm, 1112688); // Daemons from the void! They must be vanquished! + } +} diff --git a/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs b/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs index 1ac218ad2..396b87dcd 100644 --- a/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs +++ b/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs @@ -170,7 +170,10 @@ public class StealableArtifacts : GenericPersistence new(Map.Malas, new Point3D(157, 608, -1), 18432, 27648, typeof(SwordDisplay5WestArtifact)), new(Map.Malas, new Point3D(187, 643, 1), 18432, 27648, typeof(Painting6NorthArtifact)), new(Map.Malas, new Point3D(146, 623, 1), 18432, 27648, typeof(Painting6WestArtifact)), - new(Map.Malas, new Point3D(178, 629, -1), 18432, 27648, typeof(ManStatuetteEastArtifact)) + new(Map.Malas, new Point3D(178, 629, -1), 18432, 27648, typeof(ManStatuetteEastArtifact)), + + // Stygian Abyss (Ter Mur) - Artifact rarity 5 + new(Map.TerMur, new Point3D(843, 665, 27), 1152, 1728, typeof(ValkyriesGlaive)) }; public static Type[] TypesOfEntries diff --git a/Projects/UOContent/Migrations/Server.Engines.MLQuests.Definitions.Agralem.v0.json b/Projects/UOContent/Migrations/Server.Engines.MLQuests.Definitions.Agralem.v0.json new file mode 100644 index 000000000..691f8d9d4 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.MLQuests.Definitions.Agralem.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.MLQuests.Definitions.Agralem" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.GargishOutcast.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.GargishOutcast.v0.json new file mode 100644 index 000000000..3541c73b2 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.GargishOutcast.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.GargishOutcast" +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/Monsters/Humanoid/Magic/GargishOutcast.cs b/Projects/UOContent/Mobiles/Monsters/Humanoid/Magic/GargishOutcast.cs new file mode 100644 index 000000000..ff5365025 --- /dev/null +++ b/Projects/UOContent/Mobiles/Monsters/Humanoid/Magic/GargishOutcast.cs @@ -0,0 +1,141 @@ +using System; +using ModernUO.Serialization; +using Server.Items; + +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class GargishOutcast : BaseCreature +{ + private long _nextSummonTick; + + [Constructible] + public GargishOutcast() : base(AIType.AI_Mage, FightMode.Closest, 10, 1) + { + SetSpeed(0.2, 0.4); + Race = Race.Gargoyle; + Title = "the Gargish Outcast"; + + if (Utility.RandomBool()) + { + Name = NameList.RandomName("gargoyle vendor"); + Female = false; + Body = 666; + } + else + { + Name = NameList.RandomName("gargoyle vendor"); + Female = true; + Body = 667; + } + + Utility.AssignRandomHair(this, true); + + if (!Female) + { + Utility.AssignRandomFacialHair(this, true); + } + + Hue = Race.Gargoyle.RandomSkinHue(); + + SetStr(150); + SetDex(150); + SetInt(150); + + SetHits(1000, 1200); + SetMana(450, 600); + + SetDamage(15, 19); + + SetDamageType(ResistanceType.Physical, 100); + + SetResistance(ResistanceType.Physical, 10, 25); + SetResistance(ResistanceType.Fire, 40, 65); + SetResistance(ResistanceType.Cold, 40, 65); + SetResistance(ResistanceType.Poison, 40, 65); + SetResistance(ResistanceType.Energy, 40, 65); + + SetSkill(SkillName.MagicResist, 120.0); + SetSkill(SkillName.Tactics, 50.1, 60.0); + SetSkill(SkillName.Throwing, 120.0); + SetSkill(SkillName.Anatomy, 0.0, 10.0); + SetSkill(SkillName.Magery, 50.0, 80.0); + SetSkill(SkillName.EvalInt, 50.0, 80.0); + SetSkill(SkillName.Meditation, 120.0); + + Fame = 12000; + Karma = -12000; + + BaseWeapon weapon = Utility.Random(3) switch + { + 0 => new Cyclone(), + 1 => new SoulGlaive(), + _ => new Boomerang() + }; + + weapon.Attributes.SpellChanneling = 1; + weapon.LootType = LootType.Blessed; + AddItem(weapon); + + var chest = new GargishClothChestType1 { Hue = Utility.RandomNeutralHue(), LootType = LootType.Blessed }; + AddItem(chest); + + var arms = new GargishClothArmsType1 { Hue = Utility.RandomNeutralHue(), LootType = LootType.Blessed }; + AddItem(arms); + + var legs = new GargishClothLegsType1 { Hue = Utility.RandomNeutralHue(), LootType = LootType.Blessed }; + AddItem(legs); + + var kilt = new GargishClothKiltType1 { Hue = Utility.RandomNeutralHue(), LootType = LootType.Blessed }; + AddItem(kilt); + + // TODO: When Mysticism is implemented, restore the ServUO 50/50 split: + // Utility.RandomBool() ? (Necromancy+SpiritSpeak) : (AI_Mystic + Mysticism+Focus) + // Also add SkillName.Throwing as a usable skill in the Mysticism spell school. + SetSkill(SkillName.Necromancy, 90.0, 105.0); + SetSkill(SkillName.SpiritSpeak, 90.0, 105.0); + } + + public override Poison PoisonImmune => Poison.Deadly; + public override bool AlwaysMurderer => true; + public override bool ReacquireOnMovement => true; + public override bool AcquireOnApproach => true; + public override int AcquireOnApproachRange => 8; + + public override WeaponAbility GetWeaponAbility() => + Weapon is BaseWeapon bw + ? Utility.RandomBool() ? bw.PrimaryAbility : bw.SecondaryAbility + : WeaponAbility.WhirlwindAttack; + + public override void GenerateLoot() + { + AddLoot(LootPack.UltraRich); + AddLoot(LootPack.MedScrolls, 2); + AddLoot(LootPack.HighScrolls, 2); + } + + // TODO: Needs world spawn entries in Ter Mur once a spawn file for the region is added. + + public override void OnThink() + { + base.OnThink(); + + if (Combatant == null || Core.TickCount < _nextSummonTick) + { + return; + } + + if (Mana > 40 && Followers + 4 <= FollowersMax) + { + var level = (int)((Skills.Necromancy.Value + Skills.SpiritSpeak.Value) / 2.0); + + var duration = TimeSpan.FromSeconds(10 + level); + var summon = new AnimatedWeapon(this, level); + + Summon(summon, false, this, Location, 0x212, duration); + summon.PlaySound(0x64A); + + _nextSummonTick = Core.TickCount + 30000; + } + } +}