feat: Adds GargishOutcast creature, Agralem quest NPC, and SA stealable (SA)

- Adds GargishOutcast: gargoyle humanoid, AI_Mage, 120 Throwing skill,
  random throwing weapon (Cyclone/SoulGlaive/Boomerang), Necromancy+
  SpiritSpeak skills, and periodic AnimatedWeapon summon; drops UltraRich
  loot. Mysticism variant deferred pending Mysticism skill implementation.
- Adds IntoTheVoidQuest (MLQuest): kill 10 WandererOfTheVoid to earn an
  AbyssReaver; one-time only. Kill targets will expand when additional
  void creature types are implemented.
- Adds Agralem: stationary Gargoyle Bladeweaver NPC and quest giver for
  IntoTheVoidQuest, registered in MLQuests.cfg
- Adds ValkyriesGlaive as a stealable artifact in Ter Mur (rarity 5)

Remaining gaps (deferred to SA creature pass):
- Mysticism skill implementation + AI_Mystic AI type
- Raptor (RaptorClaw drop), StoneSlith (StoneSlithClaw drop)
- PrimevalLich champion (BansheesCall), Renowned creatures (StormCaller)
- GlaiveOfTheInfiniteSwarm + Vrulkax transformation service
- GatheringOfEvidence quest (WindOfCorruption reward)
- World spawns for Agralem and GargishOutcast in Ter Mur
This commit is contained in:
Joe Valentine 2026-03-15 20:49:26 -04:00 committed by Kamron Batman
parent cf3efaa2b7
commit e606daba9b
6 changed files with 226 additions and 1 deletions

View file

@ -305,3 +305,6 @@ PointyEars Gorrow Drithen Sledge
# Lost items
LostAndFound BatteredBucket
# Stygian Abyss
IntoTheVoidQuest Agralem

View file

@ -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!
}
}

View file

@ -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

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Engines.MLQuests.Definitions.Agralem"
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Mobiles.GargishOutcast"
}

View file

@ -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;
}
}
}