425 lines
13 KiB
C#
425 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Server.Engines.Quests;
|
|
using Server.Engines.Quests.Necro;
|
|
using Server.Items;
|
|
using Server.Mobiles;
|
|
using Server.Targeting;
|
|
using Server.Utilities;
|
|
|
|
namespace Server.Spells.Necromancy
|
|
{
|
|
public class AnimateDeadSpell : NecromancerSpell, ISpellTargetingItem
|
|
{
|
|
private static readonly SpellInfo m_Info = new SpellInfo(
|
|
"Animate Dead",
|
|
"Uus Corp",
|
|
203,
|
|
9031,
|
|
Reagent.GraveDust,
|
|
Reagent.DaemonBlood
|
|
);
|
|
|
|
private static readonly CreatureGroup[] m_Groups =
|
|
{
|
|
// Undead group--empty
|
|
new CreatureGroup(SlayerGroup.GetEntryByName(SlayerName.Silver).Types, Array.Empty<SummonEntry>()),
|
|
// Insects
|
|
new CreatureGroup(
|
|
new[]
|
|
{
|
|
typeof(DreadSpider), typeof(FrostSpider), typeof(GiantSpider), typeof(GiantBlackWidow),
|
|
typeof(BlackSolenInfiltratorQueen), typeof(BlackSolenInfiltratorWarrior),
|
|
typeof(BlackSolenQueen), typeof(BlackSolenWarrior), typeof(BlackSolenWorker),
|
|
typeof(RedSolenInfiltratorQueen), typeof(RedSolenInfiltratorWarrior),
|
|
typeof(RedSolenQueen), typeof(RedSolenWarrior), typeof(RedSolenWorker),
|
|
typeof(TerathanAvenger), typeof(TerathanDrone), typeof(TerathanMatriarch),
|
|
typeof(TerathanWarrior)
|
|
// TODO: Giant beetle? Ant lion? Ophidians?
|
|
},
|
|
new[]
|
|
{
|
|
new SummonEntry(0, typeof(MoundOfMaggots))
|
|
}
|
|
),
|
|
// Mounts
|
|
new CreatureGroup(
|
|
new[]
|
|
{
|
|
typeof(Horse), typeof(Nightmare), typeof(FireSteed),
|
|
typeof(Kirin), typeof(Unicorn)
|
|
},
|
|
new[]
|
|
{
|
|
new SummonEntry(10000, typeof(HellSteed)),
|
|
new SummonEntry(0, typeof(SkeletalMount))
|
|
}
|
|
),
|
|
// Elementals
|
|
new CreatureGroup(
|
|
new[]
|
|
{
|
|
typeof(BloodElemental), typeof(EarthElemental), typeof(SummonedEarthElemental),
|
|
typeof(AgapiteElemental), typeof(BronzeElemental), typeof(CopperElemental),
|
|
typeof(DullCopperElemental), typeof(GoldenElemental), typeof(ShadowIronElemental),
|
|
typeof(ValoriteElemental), typeof(VeriteElemental), typeof(PoisonElemental),
|
|
typeof(FireElemental), typeof(SummonedFireElemental), typeof(SnowElemental),
|
|
typeof(AirElemental), typeof(SummonedAirElemental), typeof(WaterElemental),
|
|
typeof(SummonedAirElemental), typeof(AcidElemental)
|
|
},
|
|
new[]
|
|
{
|
|
new SummonEntry(5000, typeof(WailingBanshee)),
|
|
new SummonEntry(0, typeof(Wraith))
|
|
}
|
|
),
|
|
// Dragons
|
|
new CreatureGroup(
|
|
new[]
|
|
{
|
|
typeof(AncientWyrm), typeof(Dragon), typeof(GreaterDragon), typeof(SerpentineDragon),
|
|
typeof(ShadowWyrm), typeof(SkeletalDragon), typeof(WhiteWyrm),
|
|
typeof(Drake), typeof(Wyvern), typeof(LesserHiryu), typeof(Hiryu)
|
|
},
|
|
new[]
|
|
{
|
|
new SummonEntry(18000, typeof(SkeletalDragon)),
|
|
new SummonEntry(10000, typeof(FleshGolem)),
|
|
new SummonEntry(5000, typeof(Lich)),
|
|
new SummonEntry(3000, typeof(SkeletalKnight), typeof(BoneKnight)),
|
|
new SummonEntry(2000, typeof(Mummy)),
|
|
new SummonEntry(1000, typeof(SkeletalMage), typeof(BoneMagi)),
|
|
new SummonEntry(0, typeof(PatchworkSkeleton))
|
|
}
|
|
),
|
|
// Default group
|
|
new CreatureGroup(
|
|
Array.Empty<Type>(),
|
|
new[]
|
|
{
|
|
new SummonEntry(18000, typeof(LichLord)),
|
|
new SummonEntry(10000, typeof(FleshGolem)),
|
|
new SummonEntry(5000, typeof(Lich)),
|
|
new SummonEntry(3000, typeof(SkeletalKnight), typeof(BoneKnight)),
|
|
new SummonEntry(2000, typeof(Mummy)),
|
|
new SummonEntry(1000, typeof(SkeletalMage), typeof(BoneMagi)),
|
|
new SummonEntry(0, typeof(PatchworkSkeleton))
|
|
}
|
|
)
|
|
};
|
|
|
|
private static readonly Dictionary<Mobile, List<Mobile>> m_Table = new Dictionary<Mobile, List<Mobile>>();
|
|
|
|
public AnimateDeadSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
|
|
{
|
|
}
|
|
|
|
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
|
|
|
|
public override double RequiredSkill => 40.0;
|
|
public override int RequiredMana => 23;
|
|
|
|
public void Target(Item item)
|
|
{
|
|
var comp = item as MaabusCoffinComponent;
|
|
|
|
if (comp?.Addon is MaabusCoffin addon)
|
|
{
|
|
var pm = Caster as PlayerMobile;
|
|
|
|
var qs = pm?.Quest;
|
|
|
|
if (qs is DarkTidesQuest)
|
|
{
|
|
QuestObjective objective = qs.FindObjective<AnimateMaabusCorpseObjective>();
|
|
|
|
if (objective?.Completed == false)
|
|
{
|
|
addon.Awake(Caster);
|
|
objective.Complete();
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!(item is Corpse c))
|
|
{
|
|
Caster.SendLocalizedMessage(1061084); // You cannot animate that.
|
|
}
|
|
else
|
|
{
|
|
Type type = null;
|
|
|
|
if (c.Owner != null)
|
|
{
|
|
type = c.Owner.GetType();
|
|
}
|
|
|
|
if (c.ItemID != 0x2006 || c.Animated || type == typeof(PlayerMobile) || type == null ||
|
|
c.Owner != null && c.Owner.Fame < 100 ||
|
|
c.Owner is BaseCreature creature && (creature.Summoned || creature.IsBonded))
|
|
{
|
|
Caster.SendLocalizedMessage(1061085); // There's not enough life force there to animate.
|
|
}
|
|
else
|
|
{
|
|
var group = FindGroup(type);
|
|
|
|
if (group != null)
|
|
{
|
|
if (group.m_Entries.Length == 0 || type == typeof(DemonKnight))
|
|
{
|
|
Caster.SendLocalizedMessage(1061086); // You cannot animate undead remains.
|
|
}
|
|
else if (CheckSequence())
|
|
{
|
|
var p = c.GetWorldLocation();
|
|
var map = c.Map;
|
|
|
|
if (map != null)
|
|
{
|
|
Effects.PlaySound(p, map, 0x1FB);
|
|
Effects.SendLocationParticles(
|
|
EffectItem.Create(p, map, EffectItem.DefaultDuration),
|
|
0x3789,
|
|
1,
|
|
40,
|
|
0x3F,
|
|
3,
|
|
9907,
|
|
0
|
|
);
|
|
|
|
Timer.DelayCall(
|
|
TimeSpan.FromSeconds(2.0),
|
|
() => SummonDelay_Callback(Caster, c, p, map, group)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FinishSequence();
|
|
}
|
|
|
|
public override void OnCast()
|
|
{
|
|
Caster.Target = new SpellTargetItem(this, TargetFlags.None, Core.ML ? 10 : 12);
|
|
Caster.SendLocalizedMessage(1061083); // Animate what corpse?
|
|
}
|
|
|
|
private static CreatureGroup FindGroup(Type type)
|
|
{
|
|
for (var i = 0; i < m_Groups.Length; ++i)
|
|
{
|
|
var group = m_Groups[i];
|
|
var types = group.m_Types;
|
|
|
|
var contains = types.Length == 0;
|
|
|
|
for (var j = 0; !contains && j < types.Length; ++j)
|
|
{
|
|
contains = types[j].IsAssignableFrom(type);
|
|
}
|
|
|
|
if (contains)
|
|
{
|
|
return @group;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static void Unregister(Mobile master, Mobile summoned)
|
|
{
|
|
if (master == null || !m_Table.TryGetValue(master, out var list))
|
|
{
|
|
return;
|
|
}
|
|
|
|
list.Remove(summoned);
|
|
|
|
if (list.Count == 0)
|
|
{
|
|
m_Table.Remove(master);
|
|
}
|
|
}
|
|
|
|
public static void Register(Mobile master, Mobile summoned)
|
|
{
|
|
if (master == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!m_Table.TryGetValue(master, out var list))
|
|
{
|
|
m_Table[master] = list = new List<Mobile>();
|
|
}
|
|
|
|
for (var i = list.Count - 1; i >= 0; --i)
|
|
{
|
|
if (i >= list.Count)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var mob = list[i];
|
|
|
|
if (mob.Deleted)
|
|
{
|
|
list.RemoveAt(i--);
|
|
}
|
|
}
|
|
|
|
list.Add(summoned);
|
|
|
|
if (list.Count > 3)
|
|
{
|
|
Timer.DelayCall(list[0].Kill);
|
|
}
|
|
|
|
Timer.DelayCall(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0), Summoned_Damage, summoned);
|
|
}
|
|
|
|
private static void Summoned_Damage(Mobile mob)
|
|
{
|
|
if (mob.Hits > 0)
|
|
{
|
|
--mob.Hits;
|
|
}
|
|
else
|
|
{
|
|
mob.Kill();
|
|
}
|
|
}
|
|
|
|
private static void SummonDelay_Callback(Mobile caster, Corpse corpse, Point3D loc, Map map, CreatureGroup group)
|
|
{
|
|
if (corpse.Animated)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var owner = corpse.Owner;
|
|
|
|
if (owner == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var necromancy = caster.Skills.Necromancy.Value;
|
|
var spiritSpeak = caster.Skills.SpiritSpeak.Value;
|
|
|
|
var casterAbility = (int)(necromancy * 30) + (int)(spiritSpeak * 70);
|
|
casterAbility = Math.Clamp(casterAbility / 10 * 18, 0, owner.Fame);
|
|
|
|
Type toSummon = null;
|
|
var entries = group.m_Entries;
|
|
|
|
for (var i = 0; toSummon == null && i < entries.Length; ++i)
|
|
{
|
|
var entry = entries[i];
|
|
|
|
if (casterAbility < entry.m_Requirement)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var animates = entry.m_ToSummon;
|
|
|
|
toSummon = animates.RandomElement();
|
|
}
|
|
|
|
if (toSummon == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Mobile summoned = null;
|
|
|
|
try
|
|
{
|
|
summoned = ActivatorUtil.CreateInstance(toSummon) as Mobile;
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
|
|
if (summoned == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (summoned is BaseCreature bc)
|
|
{
|
|
// to be sure
|
|
bc.Tamable = false;
|
|
|
|
bc.ControlSlots = bc is BaseMount ? 1 : 0;
|
|
|
|
Effects.PlaySound(loc, map, bc.GetAngerSound());
|
|
|
|
BaseCreature.Summon(bc, false, caster, loc, 0x28, TimeSpan.FromDays(1.0));
|
|
}
|
|
|
|
if (summoned is SkeletalDragon dragon)
|
|
{
|
|
Scale(dragon, 50); // lose 50% hp and strength
|
|
}
|
|
|
|
summoned.Fame = 0;
|
|
summoned.Karma = -1500;
|
|
|
|
summoned.MoveToWorld(loc, map);
|
|
|
|
corpse.Hue = 1109;
|
|
corpse.Animated = true;
|
|
|
|
Register(caster, summoned);
|
|
}
|
|
|
|
public static void Scale(BaseCreature bc, int scalar)
|
|
{
|
|
var toScale = bc.RawStr;
|
|
bc.RawStr = AOS.Scale(toScale, scalar);
|
|
|
|
toScale = bc.HitsMaxSeed;
|
|
|
|
if (toScale > 0)
|
|
{
|
|
bc.HitsMaxSeed = AOS.Scale(toScale, scalar);
|
|
}
|
|
|
|
bc.Hits = bc.Hits; // refresh hits
|
|
}
|
|
|
|
private class CreatureGroup
|
|
{
|
|
public readonly SummonEntry[] m_Entries;
|
|
public readonly Type[] m_Types;
|
|
|
|
public CreatureGroup(Type[] types, SummonEntry[] entries)
|
|
{
|
|
m_Types = types;
|
|
m_Entries = entries;
|
|
}
|
|
}
|
|
|
|
private class SummonEntry
|
|
{
|
|
public readonly int m_Requirement;
|
|
public readonly Type[] m_ToSummon;
|
|
|
|
public SummonEntry(int requirement, params Type[] toSummon)
|
|
{
|
|
m_ToSummon = toSummon;
|
|
m_Requirement = requirement;
|
|
}
|
|
}
|
|
}
|
|
}
|