Reorganizes Project (#41)

This commit is contained in:
Kamron Batman 2019-08-02 18:13:40 -07:00 committed by GitHub
parent 08bf44af9a
commit 3614a66aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3499 changed files with 79 additions and 55 deletions

View file

@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Items;
using Server.Mobiles;
namespace Server.Spells.Spellweaving
{
public class ArcaneCircleSpell : ArcanistSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Arcane Circle", "Myrshalee",
-1
);
public ArcaneCircleSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.5);
public override double RequiredSkill => 0.0;
public override int RequiredMana => 24;
public override bool CheckCast()
{
if (!IsValidLocation(Caster.Location, Caster.Map))
{
Caster.SendLocalizedMessage(
1072705); // You must be standing on an arcane circle, pentagram or abbatoir to use this spell.
return false;
}
if (GetArcanists().Count < 2)
{
Caster.SendLocalizedMessage(1080452); //There are not enough spellweavers present to create an Arcane Focus.
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
if (CheckSequence())
{
Caster.FixedParticles(0x3779, 10, 20, 0x0, EffectLayer.Waist);
Caster.PlaySound(0x5C0);
List<Mobile> Arcanists = GetArcanists();
TimeSpan duration = TimeSpan.FromHours(Math.Max(1, (int)(Caster.Skills.Spellweaving.Value / 24)));
int strengthBonus =
Math.Min(Arcanists.Count,
IsSanctuary(Caster.Location, Caster.Map)
? 6
: 5); //The Sanctuary is a special, single location place
for (int i = 0; i < Arcanists.Count; i++)
GiveArcaneFocus(Arcanists[i], duration, strengthBonus);
}
FinishSequence();
}
private static bool IsSanctuary(Point3D p, Map m)
{
return (m == Map.Trammel || m == Map.Felucca) && p.X == 6267 && p.Y == 131;
}
private static bool IsValidLocation(Point3D location, Map map)
{
LandTile lt = map.Tiles.GetLandTile(location.X, location.Y); // Land Tiles
if (IsValidTile(lt.ID) && lt.Z == location.Z)
return true;
StaticTile[] tiles = map.Tiles.GetStaticTiles(location.X, location.Y); // Static Tiles
for (int i = 0; i < tiles.Length; ++i)
{
StaticTile t = tiles[i];
ItemData id = TileData.ItemTable[t.ID & TileData.MaxItemValue];
int tand = t.ID;
if (t.Z + id.CalcHeight != location.Z)
continue;
if (IsValidTile(tand))
return true;
}
IPooledEnumerable<Item> eable = map.GetItemsInRange(location, 0);
bool found = eable.Any(item =>
item.Z + item.ItemData.CalcHeight == location.Z && IsValidTile(item.ItemID));
eable.Free();
return found;
}
public static bool IsValidTile(int itemID)
{
//Per OSI, Center tile only
return itemID == 0xFEA || itemID == 0x1216 || itemID == 0x307F || itemID == 0x1D10 || itemID == 0x1D0F ||
itemID == 0x1D1F ||
itemID == 0x1D12; // Pentagram center, Abbatoir center, Arcane Circle Center, Bloody Pentagram has 4 tiles at center
}
private List<Mobile> GetArcanists()
{
List<Mobile> weavers = new List<Mobile> { Caster };
//OSI Verified: Even enemies/combatants count
// Everyone gets the Arcane Focus, power capped elsewhere
weavers.AddRange(Caster.GetMobilesInRange(1)
.Where(m => m != Caster && m is PlayerMobile && Caster.CanBeBeneficial(m, false) &&
Math.Abs(Caster.Skills.Spellweaving.Value - m.Skills.Spellweaving.Value) <= 20));
return weavers;
}
private void GiveArcaneFocus(Mobile to, TimeSpan duration, int strengthBonus)
{
if (to == null) //Sanity
return;
ArcaneFocus focus = FindArcaneFocus(to);
if (focus == null)
{
focus = new ArcaneFocus(duration, strengthBonus);
if (to.PlaceInBackpack(focus))
{
focus.SendTimeRemainingMessage(to);
to.SendLocalizedMessage(1072740); // An arcane focus appears in your backpack.
}
else
{
focus.Delete();
}
}
else //OSI renewal rules: the new one will override the old one, always.
{
to.SendLocalizedMessage(1072828); // Your arcane focus is renewed.
focus.LifeSpan = duration;
focus.CreationTime = DateTime.UtcNow;
focus.StrengthBonus = strengthBonus;
focus.InvalidateProperties();
focus.SendTimeRemainingMessage(to);
}
}
}
}

View file

@ -0,0 +1,47 @@
namespace Server.Spells.Spellweaving
{
public abstract class ArcaneForm : ArcanistSpell, ITransformationSpell
{
public ArcaneForm(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info)
{
}
public abstract int Body{ get; }
public virtual int Hue => 0;
public virtual int PhysResistOffset => 0;
public virtual int FireResistOffset => 0;
public virtual int ColdResistOffset => 0;
public virtual int PoisResistOffset => 0;
public virtual int NrgyResistOffset => 0;
public virtual double TickRate => 1.0;
public virtual void OnTick(Mobile m)
{
}
public virtual void DoEffect(Mobile m)
{
}
public virtual void RemoveEffect(Mobile m)
{
}
public override bool CheckCast()
{
if (!TransformationSpellHelper.CheckCast(Caster, this))
return false;
return base.CheckCast();
}
public override void OnCast()
{
TransformationSpellHelper.OnCast(Caster, this);
FinishSequence();
}
}
}

View file

@ -0,0 +1,56 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Spellweaving
{
public abstract class ArcaneSummon<T> : ArcanistSpell where T : BaseCreature
{
public ArcaneSummon(Mobile caster, Item scroll, SpellInfo info)
: base(caster, scroll, info)
{
}
public abstract int Sound{ get; }
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1074270); // You have too many followers to summon another one.
return false;
}
return true;
}
public override void OnCast()
{
if (CheckSequence())
{
TimeSpan duration = TimeSpan.FromMinutes(Caster.Skills.Spellweaving.Value / 24 + FocusLevel * 2);
int summons = Math.Min(1 + FocusLevel, Caster.FollowersMax - Caster.Followers);
for (int i = 0; i < summons; i++)
{
BaseCreature bc;
try
{
bc = Activator.CreateInstance<T>();
}
catch
{
break;
}
SpellHelper.Summon(bc, Caster, Sound, duration, false, false);
}
FinishSequence();
}
}
}
}

View file

@ -0,0 +1,146 @@
using Server.Engines.MLQuests;
using Server.Items;
using Server.Mobiles;
namespace Server.Spells.Spellweaving
{
public abstract class ArcanistSpell : Spell
{
private int m_CastTimeFocusLevel;
public ArcanistSpell(Mobile caster, Item scroll, SpellInfo info)
: base(caster, scroll, info)
{
}
public abstract double RequiredSkill{ get; }
public abstract int RequiredMana{ get; }
public override SkillName CastSkill => SkillName.Spellweaving;
public override SkillName DamageSkill => SkillName.Spellweaving;
public override bool ClearHandsOnCast => false;
public virtual int FocusLevel => m_CastTimeFocusLevel;
public static int GetFocusLevel(Mobile from)
{
ArcaneFocus focus = FindArcaneFocus(from);
return focus?.Deleted != false ? 0 : focus.StrengthBonus;
}
public static ArcaneFocus FindArcaneFocus(Mobile from)
{
return from.Holding as ArcaneFocus ?? from.Backpack?.FindItemByType<ArcaneFocus>();
}
public static bool CheckExpansion(Mobile from)
{
return !(from is PlayerMobile) || from.NetState?.SupportsExpansion(Expansion.ML) == true;
}
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
Mobile caster = Caster;
if (!CheckExpansion(caster))
{
caster.SendLocalizedMessage(
1072176); // You must upgrade to the Mondain's Legacy Expansion Pack before using that ability
return false;
}
if (caster is PlayerMobile mobile)
{
MLQuestContext context = MLQuestSystem.GetContext(mobile);
if (context == null || !context.Spellweaving)
{
mobile.SendLocalizedMessage(
1073220); // You must have completed the epic arcanist quest to use this ability.
return false;
}
}
int mana = ScaleMana(RequiredMana);
if (caster.Mana < mana)
{
caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
if (caster.Skills[CastSkill].Value < RequiredSkill)
{
caster.SendLocalizedMessage(1063013,
$"{RequiredSkill:F1}\t{"#1044114"}"); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return false;
}
return true;
}
public override void GetCastSkills(out double min, out double max)
{
min = RequiredSkill - 12.5; //per 5 on Friday, 2/16/07
max = RequiredSkill + 37.5;
}
public override int GetMana()
{
return RequiredMana;
}
public override void DoFizzle()
{
Caster.PlaySound(0x1D6);
Caster.NextSpellTime = Core.TickCount;
}
public override void DoHurtFizzle()
{
Caster.PlaySound(0x1D6);
}
public override void OnDisturb(DisturbType type, bool message)
{
base.OnDisturb(type, message);
if (message)
Caster.PlaySound(0x1D6);
}
public override void OnBeginCast()
{
base.OnBeginCast();
SendCastEffect();
m_CastTimeFocusLevel = GetFocusLevel(Caster);
}
public virtual void SendCastEffect()
{
Caster.FixedEffect(0x37C4, 10, (int)(GetCastDelay().TotalSeconds * 28), 4, 3);
}
public virtual bool CheckResisted(Mobile m)
{
double percent =
(50 + 2 * (GetResistSkill(m) - GetDamageSkill(Caster))) /
100; //TODO: According to the guide this is it.. but.. is it correct per OSI?
if (percent <= 0)
return false;
if (percent >= 1.0)
return true;
return percent >= Utility.RandomDouble();
}
}
}

View file

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
namespace Server.Spells.Spellweaving
{
public class AttuneWeaponSpell : ArcanistSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Attune Weapon", "Haeldril",
-1
);
private static Dictionary<Mobile, ExpireTimer> m_Table = new Dictionary<Mobile, ExpireTimer>();
public AttuneWeaponSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 0.0;
public override int RequiredMana => 24;
public override bool CheckCast()
{
if (m_Table.ContainsKey(Caster))
{
Caster.SendLocalizedMessage(501775); // This spell is already in effect.
return false;
}
if (Caster.CanBeginAction<AttuneWeaponSpell>())
return base.CheckCast();
Caster.SendLocalizedMessage(1075124); // You must wait before casting that spell again.
return false;
}
public override void OnCast()
{
if (CheckSequence())
{
Caster.PlaySound(0x5C3);
Caster.FixedParticles(0x3728, 1, 13, 0x26B8, 0x455, 7, EffectLayer.Waist);
Caster.FixedParticles(0x3779, 1, 15, 0x251E, 0x3F, 7, EffectLayer.Waist);
double skill = Caster.Skills.Spellweaving.Value;
int damageAbsorb = (int)(18 + (skill - 10) / 10 * 3 + FocusLevel * 6);
Caster.MeleeDamageAbsorb = damageAbsorb;
TimeSpan duration = TimeSpan.FromSeconds(60 + FocusLevel * 12);
ExpireTimer t = new ExpireTimer(Caster, duration);
t.Start();
m_Table[Caster] = t;
Caster.BeginAction<AttuneWeaponSpell>();
BuffInfo.AddBuff(Caster,
new BuffInfo(BuffIcon.AttuneWeapon, 1075798, duration, Caster, damageAbsorb.ToString()));
}
FinishSequence();
}
public static void TryAbsorb(Mobile defender, ref int damage)
{
if (damage == 0 || !IsAbsorbing(defender) || defender.MeleeDamageAbsorb <= 0)
return;
int absorbed = Math.Min(damage, defender.MeleeDamageAbsorb);
damage -= absorbed;
defender.MeleeDamageAbsorb -= absorbed;
defender.SendLocalizedMessage(1075127,
$"{absorbed}\t{defender.MeleeDamageAbsorb}"); // ~1_damage~ point(s) of damage have been absorbed. A total of ~2_remaining~ point(s) of shielding remain.
if (defender.MeleeDamageAbsorb <= 0)
StopAbsorbing(defender, true);
}
public static bool IsAbsorbing(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static void StopAbsorbing(Mobile m, bool message)
{
if (m_Table.TryGetValue(m, out ExpireTimer t))
t.DoExpire(message);
}
private class ExpireTimer : Timer
{
private Mobile m_Mobile;
public ExpireTimer(Mobile m, TimeSpan delay)
: base(delay)
{
m_Mobile = m;
}
protected override void OnTick()
{
DoExpire(true);
}
public void DoExpire(bool message)
{
Stop();
m_Mobile.MeleeDamageAbsorb = 0;
if (message)
{
m_Mobile.SendLocalizedMessage(1075126); // Your attunement fades.
m_Mobile.PlaySound(0x1F8);
}
m_Table.Remove(m_Mobile);
DelayCall(TimeSpan.FromSeconds(120), delegate { m_Mobile.EndAction<AttuneWeaponSpell>(); });
BuffInfo.RemoveBuff(m_Mobile, BuffIcon.AttuneWeapon);
}
}
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
namespace Server.Spells.Spellweaving
{
public class EssenceOfWindSpell : ArcanistSpell
{
private static SpellInfo m_Info = new SpellInfo("Essence of Wind", "Anathrae", -1);
private static Dictionary<Mobile, EssenceOfWindInfo> m_Table = new Dictionary<Mobile, EssenceOfWindInfo>();
public EssenceOfWindSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(3.0);
public override double RequiredSkill => 52.0;
public override int RequiredMana => 40;
public override void OnCast()
{
if (CheckSequence())
{
Caster.PlaySound(0x5C6);
int range = 5 + FocusLevel;
int damage = 25 + FocusLevel;
double skill = Caster.Skills.Spellweaving.Value;
TimeSpan duration = TimeSpan.FromSeconds((int)(skill / 24) + FocusLevel);
int fcMalus = FocusLevel + 1;
int ssiMalus = 2 * (FocusLevel + 1);
IPooledEnumerable<Mobile> eable = Caster.GetMobilesInRange(range);
foreach (Mobile m in eable)
{
if (Caster == m || !Caster.InLOS(m) || !SpellHelper.ValidIndirectTarget(Caster, m) ||
!Caster.CanBeHarmful(m, false))
continue;
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0);
if (CheckResisted(m))
continue;
m_Table[m] = new EssenceOfWindInfo(m, fcMalus, ssiMalus, duration);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.EssenceOfWind, 1075802, duration, m,
$"{fcMalus.ToString()}\t{ssiMalus.ToString()}"));
}
eable.Free();
}
FinishSequence();
}
public static int GetFCMalus(Mobile m)
{
return m_Table.TryGetValue(m, out EssenceOfWindInfo info) ? info.FCMalus : 0;
}
public static int GetSSIMalus(Mobile m)
{
return m_Table.TryGetValue(m, out EssenceOfWindInfo info) ? info.SSIMalus : 0;
}
public static bool IsDebuffed(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static void StopDebuffing(Mobile m, bool message)
{
if (m_Table.TryGetValue(m, out EssenceOfWindInfo info))
info.Timer.DoExpire(message);
}
private class EssenceOfWindInfo
{
public EssenceOfWindInfo(Mobile defender, int fcMalus, int ssiMalus, TimeSpan duration)
{
Defender = defender;
FCMalus = fcMalus;
SSIMalus = ssiMalus;
Timer = new ExpireTimer(Defender, duration);
Timer.Start();
}
public Mobile Defender{ get; }
public int FCMalus{ get; }
public int SSIMalus{ get; }
public ExpireTimer Timer{ get; }
}
private class ExpireTimer : Timer
{
private Mobile m_Mobile;
public ExpireTimer(Mobile m, TimeSpan delay) : base(delay)
{
m_Mobile = m;
}
protected override void OnTick()
{
DoExpire(true);
}
public void DoExpire(bool message)
{
Stop();
m_Table.Remove(m_Mobile);
BuffInfo.RemoveBuff(m_Mobile, BuffIcon.EssenceOfWind);
}
}
}
}

View file

@ -0,0 +1,76 @@
using System;
namespace Server.Spells.Spellweaving
{
public class EtherealVoyageSpell : ArcaneForm
{
private static SpellInfo m_Info = new SpellInfo(
"Ethereal Voyage", "Orlavdra",
-1
);
public EtherealVoyageSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(3.5);
public override double RequiredSkill => 24.0;
public override int RequiredMana => 32;
public override int Body => 0x302;
public override int Hue => 0x48F;
public static void Initialize()
{
EventSink.AggressiveAction += delegate(AggressiveActionEventArgs e)
{
if (TransformationSpellHelper.UnderTransformation(e.Aggressor, typeof(EtherealVoyageSpell)))
TransformationSpellHelper.RemoveContext(e.Aggressor, true);
};
}
public override bool CheckCast()
{
if (TransformationSpellHelper.UnderTransformation(Caster, typeof(EtherealVoyageSpell)))
Caster.SendLocalizedMessage(501775); // This spell is already in effect.
else if (!Caster.CanBeginAction<EtherealVoyageSpell>())
Caster.SendLocalizedMessage(1075124); // You must wait before casting that spell again.
else if (Caster.Combatant != null)
Caster.SendLocalizedMessage(1072586); // You cannot cast Ethereal Voyage while you are in combat.
else
return base.CheckCast();
return false;
}
public override void DoEffect(Mobile m)
{
m.PlaySound(0x5C8);
m.SendLocalizedMessage(1074770); // You are now under the effects of Ethereal Voyage.
double skill = Caster.Skills.Spellweaving.Value;
TimeSpan duration = TimeSpan.FromSeconds(12 + (int)(skill / 24) + FocusLevel * 2);
Timer.DelayCall(duration, RemoveEffect, Caster);
Caster.BeginAction(
typeof(EtherealVoyageSpell)); //Cannot cast this spell for another 5 minutes(300sec) after effect removed.
BuffInfo.AddBuff(Caster, new BuffInfo(BuffIcon.EtherealVoyage, 1031613, 1075805, duration, Caster));
}
public override void RemoveEffect(Mobile m)
{
m.SendLocalizedMessage(1074771); // You are no longer under the effects of Ethereal Voyage.
TransformationSpellHelper.RemoveContext(m, true);
Timer.DelayCall(TimeSpan.FromMinutes(5), delegate { m.EndAction<EtherealVoyageSpell>(); });
BuffInfo.RemoveBuff(m, BuffIcon.EtherealVoyage);
}
}
}

View file

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Spellweaving
{
public class GiftOfLifeSpell : ArcanistSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Gift of Life", "Illorae",
-1
);
private static Dictionary<Mobile, ExpireTimer> m_Table = new Dictionary<Mobile, ExpireTimer>();
public GiftOfLifeSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(4.0);
public override double RequiredSkill => 38.0;
public override int RequiredMana => 70;
public double HitsScalar => (Caster.Skills.Spellweaving.Value / 2.4 + FocusLevel) / 100;
public static void Initialize()
{
EventSink.PlayerDeath += delegate(PlayerDeathEventArgs e) { HandleDeath(e.Mobile); };
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, 10);
}
public void Target(Mobile m)
{
if (m == null)
Caster.SendLocalizedMessage(1072077); // You may only cast this spell on yourself or a bonded pet.
else if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (m.IsDeadBondedPet || !m.Alive)
{
// As per Osi: Nothing happens.
}
else if (m != Caster && !(m is BaseCreature bc && bc.IsBonded && bc.ControlMaster == Caster))
Caster.SendLocalizedMessage(1072077); // You may only cast this spell on yourself or a bonded pet.
else if (m_Table.ContainsKey(m))
Caster.SendLocalizedMessage(501775); // This spell is already in effect.
else if (CheckBSequence(m))
{
if (Caster == m)
{
Caster.SendLocalizedMessage(1074774); // You weave powerful magic, protecting yourself from death.
}
else
{
Caster.SendLocalizedMessage(1074775); // You weave powerful magic, protecting your pet from death.
SpellHelper.Turn(Caster, m);
}
m.PlaySound(0x244);
m.FixedParticles(0x3709, 1, 30, 0x26ED, 5, 2, EffectLayer.Waist);
m.FixedParticles(0x376A, 1, 30, 0x251E, 5, 3, EffectLayer.Waist);
double skill = Caster.Skills.Spellweaving.Value;
TimeSpan duration = TimeSpan.FromMinutes((int)(skill / 24) * 2 + FocusLevel);
ExpireTimer t = new ExpireTimer(m, duration, this);
t.Start();
m_Table[m] = t;
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.GiftOfLife, 1031615, 1075807, duration, m, null, true));
}
FinishSequence();
}
public static void HandleDeath(Mobile m)
{
if (m_Table.ContainsKey(m))
Timer.DelayCall(TimeSpan.FromSeconds(Utility.RandomMinMax(2, 4)), HandleDeath_OnCallback, m);
}
private static void HandleDeath_OnCallback(Mobile m)
{
if (!m_Table.TryGetValue(m, out ExpireTimer timer))
return;
double hitsScalar = timer.Spell.HitsScalar;
if (m is BaseCreature pet && pet.IsDeadBondedPet)
{
Mobile master = pet.GetMaster();
if (master?.NetState != null && Utility.InUpdateRange(pet, master))
{
master.CloseGump<PetResurrectGump>();
master.SendGump(new PetResurrectGump(master, pet, hitsScalar));
}
else
{
List<Mobile> friends = pet.Friends;
for (int i = 0; friends != null && i < friends.Count; i++)
{
Mobile friend = friends[i];
if (friend.NetState != null && Utility.InUpdateRange(pet, friend))
{
friend.CloseGump<PetResurrectGump>();
friend.SendGump(new PetResurrectGump(friend, pet));
break;
}
}
}
}
else
{
m.CloseGump<ResurrectGump>();
m.SendGump(new ResurrectGump(m, hitsScalar));
}
//Per OSI, buff is removed when gump sent, irregardless of online status or acceptance
timer.DoExpire();
}
public static void OnLogin(LoginEventArgs e)
{
Mobile m = e.Mobile;
if (m == null || m.Alive || m_Table[m] == null)
return;
HandleDeath_OnCallback(m);
}
private class ExpireTimer : Timer
{
private Mobile m_Mobile;
public ExpireTimer(Mobile m, TimeSpan delay, GiftOfLifeSpell spell)
: base(delay)
{
m_Mobile = m;
Spell = spell;
}
public GiftOfLifeSpell Spell{ get; }
protected override void OnTick()
{
DoExpire();
}
public void DoExpire()
{
Stop();
m_Mobile.SendLocalizedMessage(1074776); // You are no longer protected with Gift of Life.
m_Table.Remove(m_Mobile);
BuffInfo.RemoveBuff(m_Mobile, BuffIcon.GiftOfLife);
}
}
}
}

View file

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using Server.Targeting;
namespace Server.Spells.Spellweaving
{
public class GiftOfRenewalSpell : ArcanistSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Gift of Renewal", "Olorisstra",
-1
);
private static Dictionary<Mobile, GiftOfRenewalInfo> m_Table = new Dictionary<Mobile, GiftOfRenewalInfo>();
public GiftOfRenewalSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(3.0);
public override double RequiredSkill => 0.0;
public override int RequiredMana => 24;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, 10);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (m_Table.ContainsKey(m))
Caster.SendLocalizedMessage(501775); // This spell is already in effect.
else if (!Caster.CanBeginAction<GiftOfRenewalSpell>())
Caster.SendLocalizedMessage(501789); // You must wait before trying again.
else if (CheckBSequence(m))
{
SpellHelper.Turn(Caster, m);
Caster.FixedEffect(0x374A, 10, 20);
Caster.PlaySound(0x5C9);
if (m.Poisoned)
{
m.CurePoison(m);
}
else
{
double skill = Caster.Skills.Spellweaving.Value;
int hitsPerRound = 5 + (int)(skill / 24) + FocusLevel;
TimeSpan duration = TimeSpan.FromSeconds(30 + FocusLevel * 10);
GiftOfRenewalInfo info = new GiftOfRenewalInfo(Caster, m, hitsPerRound);
Timer.DelayCall(duration,
delegate
{
if (StopEffect(m))
{
m.PlaySound(0x455);
m.SendLocalizedMessage(1075071); // The Gift of Renewal has faded.
}
});
m_Table[m] = info;
Caster.BeginAction<GiftOfRenewalSpell>();
BuffInfo.AddBuff(m,
new BuffInfo(BuffIcon.GiftOfRenewal, 1031602, 1075797, duration, m, hitsPerRound.ToString()));
}
}
FinishSequence();
}
public static bool StopEffect(Mobile m)
{
if (!m_Table.TryGetValue(m, out GiftOfRenewalInfo info))
return false;
m_Table.Remove(m);
info.m_Timer.Stop();
BuffInfo.RemoveBuff(m, BuffIcon.GiftOfRenewal);
Timer.DelayCall(TimeSpan.FromSeconds(60), delegate { info.m_Caster.EndAction<GiftOfRenewalSpell>(); });
return true;
}
private class GiftOfRenewalInfo
{
public Mobile m_Caster;
public int m_HitsPerRound;
public Mobile m_Mobile;
public InternalTimer m_Timer;
public GiftOfRenewalInfo(Mobile caster, Mobile mobile, int hitsPerRound)
{
m_Caster = caster;
m_Mobile = mobile;
m_HitsPerRound = hitsPerRound;
m_Timer = new InternalTimer(this);
m_Timer.Start();
}
}
private class InternalTimer : Timer
{
private GiftOfRenewalInfo m_GiftInfo;
public InternalTimer(GiftOfRenewalInfo info)
: base(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0))
{
m_GiftInfo = info;
}
protected override void OnTick()
{
Mobile m = m_GiftInfo.m_Mobile;
if (!m_Table.ContainsKey(m))
{
Stop();
return;
}
if (!m.Alive)
{
Stop();
StopEffect(m);
return;
}
if (m.Hits >= m.HitsMax)
return;
int toHeal = m_GiftInfo.m_HitsPerRound;
SpellHelper.Heal(toHeal, m, m_GiftInfo.m_Caster);
m.FixedParticles(0x376A, 9, 32, 5005, EffectLayer.Waist);
}
}
}
}

View file

@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using Server.Items;
namespace Server.Spells.Spellweaving
{
public class ImmolatingWeaponSpell : ArcanistSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Immolating Weapon", "Thalshara",
-1
);
private static Dictionary<BaseWeapon, ImmolatingWeaponEntry> m_WeaponDamageTable =
new Dictionary<BaseWeapon, ImmolatingWeaponEntry>();
public ImmolatingWeaponSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 10.0;
public override int RequiredMana => 32;
public override bool CheckCast()
{
if (!(Caster.Weapon is BaseWeapon weapon) || weapon is Fists || weapon is BaseRanged)
{
Caster.SendLocalizedMessage(1060179); // You must be wielding a weapon to use this ability!
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
if (!(Caster.Weapon is BaseWeapon weapon) || weapon is Fists || weapon is BaseRanged)
{
Caster.SendLocalizedMessage(1060179); // You must be wielding a weapon to use this ability!
}
else if (CheckSequence())
{
Caster.PlaySound(0x5CA);
Caster.FixedParticles(0x36BD, 20, 10, 5044, EffectLayer.Head);
if (!IsImmolating(weapon)) // On OSI, the effect is not re-applied
{
double skill = Caster.Skills.Spellweaving.Value;
int duration = 10 + (int)(skill / 24) + FocusLevel;
int damage = 5 + (int)(skill / 24) + FocusLevel;
Timer stopTimer = Timer.DelayCall(TimeSpan.FromSeconds(duration), StopImmolating, weapon);
m_WeaponDamageTable[weapon] = new ImmolatingWeaponEntry(damage, stopTimer, Caster);
weapon.InvalidateProperties();
}
}
FinishSequence();
}
public static bool IsImmolating(BaseWeapon weapon)
{
return m_WeaponDamageTable.ContainsKey(weapon);
}
public static int GetImmolatingDamage(BaseWeapon weapon)
{
return m_WeaponDamageTable.TryGetValue(weapon, out ImmolatingWeaponEntry entry) ? entry.m_Damage : 0;
}
public static void DoEffect(BaseWeapon weapon, Mobile target)
{
Timer.DelayCall(TimeSpan.FromSeconds(0.25), FinishEffect, new DelayedEffectEntry(weapon, target));
}
private static void FinishEffect(DelayedEffectEntry effect)
{
if (m_WeaponDamageTable.TryGetValue(effect.m_Weapon, out ImmolatingWeaponEntry entry))
AOS.Damage(effect.m_Target, entry.m_Caster, entry.m_Damage, 0, 100, 0, 0, 0);
}
public static void StopImmolating(BaseWeapon weapon)
{
if (!m_WeaponDamageTable.TryGetValue(weapon, out ImmolatingWeaponEntry entry))
return;
entry.m_Caster?.PlaySound(0x27);
entry.m_Timer.Stop();
m_WeaponDamageTable.Remove(weapon);
weapon.InvalidateProperties();
}
private class ImmolatingWeaponEntry
{
public Mobile m_Caster;
public int m_Damage;
public Timer m_Timer;
public ImmolatingWeaponEntry(int damage, Timer stopTimer, Mobile caster)
{
m_Damage = damage;
m_Timer = stopTimer;
m_Caster = caster;
}
}
private class DelayedEffectEntry
{
public Mobile m_Target;
public BaseWeapon m_Weapon;
public DelayedEffectEntry(BaseWeapon weapon, Mobile target)
{
m_Weapon = weapon;
m_Target = target;
}
}
}
}

View file

@ -0,0 +1,60 @@
using System;
namespace Server.Items
{
public class ArcaneFocus : TransientItem
{
[Constructible]
public ArcaneFocus()
: this(TimeSpan.FromHours(1), 1)
{
}
[Constructible]
public ArcaneFocus(int lifeSpan, int strengthBonus)
: this(TimeSpan.FromSeconds(lifeSpan), strengthBonus)
{
}
public ArcaneFocus(TimeSpan lifeSpan, int strengthBonus) : base(0x3155, lifeSpan)
{
LootType = LootType.Blessed;
StrengthBonus = strengthBonus;
}
public ArcaneFocus(Serial serial) : base(serial)
{
}
public override int LabelNumber => 1032629; // Arcane Focus
[CommandProperty(AccessLevel.GameMaster)]
public int StrengthBonus{ get; set; }
public override TextDefinition InvalidTransferMessage => 1073480; // Your arcane focus disappears.
public override bool Nontransferable => true;
public override void GetProperties(ObjectPropertyList list)
{
base.GetProperties(list);
list.Add(1060485, StrengthBonus.ToString()); // strength bonus ~1_val~
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(StrengthBonus);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
StrengthBonus = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,102 @@
using System;
namespace Server.Items
{
public class TransientItem : Item
{
private Timer m_Timer;
[Constructible]
public TransientItem(int itemID, TimeSpan lifeSpan)
: base(itemID)
{
CreationTime = DateTime.UtcNow;
LifeSpan = lifeSpan;
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), CheckExpiry);
}
public TransientItem(Serial serial)
: base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TimeSpan LifeSpan{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DateTime CreationTime{ get; set; }
public override bool Nontransferable => true;
public virtual TextDefinition InvalidTransferMessage => null;
public override void HandleInvalidTransfer(Mobile from)
{
if (InvalidTransferMessage != null)
TextDefinition.SendMessageTo(from, InvalidTransferMessage);
Delete();
}
public virtual void Expire(Mobile parent)
{
parent?.SendLocalizedMessage(1072515, Name ?? $"#{LabelNumber}"); // The ~1_name~ expired...
Effects.PlaySound(GetWorldLocation(), Map, 0x201);
Delete();
}
public virtual void SendTimeRemainingMessage(Mobile to)
{
to.SendLocalizedMessage(1072516,
$"{Name ?? $"#{LabelNumber}"}\t{(int)LifeSpan.TotalSeconds}"); // ~1_name~ will expire in ~2_val~ seconds!
}
public override void OnDelete()
{
m_Timer?.Stop();
base.OnDelete();
}
public virtual void CheckExpiry()
{
if (CreationTime + LifeSpan < DateTime.UtcNow)
Expire(RootParent as Mobile);
else
InvalidateProperties();
}
public override void GetProperties(ObjectPropertyList list)
{
base.GetProperties(list);
TimeSpan remaining = CreationTime + LifeSpan - DateTime.UtcNow;
list.Add(1072517, ((int)remaining.TotalSeconds).ToString()); // Lifespan: ~1_val~ seconds
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(LifeSpan);
writer.Write(CreationTime);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
LifeSpan = reader.ReadTimeSpan();
CreationTime = reader.ReadDateTime();
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), CheckExpiry);
}
}
}

View file

@ -0,0 +1,62 @@
namespace Server.Mobiles
{
public class ArcaneFey : BaseCreature
{
[Constructible]
public ArcaneFey() : base(AIType.AI_Mage, FightMode.Evil, 10, 1, 0.2, 0.4)
{
Name = NameList.RandomName("pixie");
Body = 128;
BaseSoundID = 0x467;
SetStr(20);
SetDex(150);
SetInt(125);
SetDamage(9, 15);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 80, 90);
SetResistance(ResistanceType.Fire, 40, 50);
SetResistance(ResistanceType.Cold, 40, 50);
SetResistance(ResistanceType.Poison, 40, 50);
SetResistance(ResistanceType.Energy, 40, 50);
SetSkill(SkillName.EvalInt, 70.1, 80.0);
SetSkill(SkillName.Magery, 70.1, 80.0);
SetSkill(SkillName.Meditation, 70.1, 80.0);
SetSkill(SkillName.MagicResist, 50.5, 100.0);
SetSkill(SkillName.Tactics, 10.1, 20.0);
SetSkill(SkillName.Wrestling, 10.1, 12.5);
Fame = 0;
Karma = 0;
ControlSlots = 1;
}
public ArcaneFey(Serial serial) : base(serial)
{
}
public override string CorpseName => "a pixie corpse";
public override double DispelDifficulty => 70.0;
public override double DispelFocus => 20.0;
public override OppositionGroup OppositionGroup => OppositionGroup.FeyAndUndead;
public override bool InitialInnocent => true;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,63 @@
namespace Server.Mobiles
{
public class ArcaneFiend : BaseCreature
{
[Constructible]
public ArcaneFiend() : base(AIType.AI_Mage, FightMode.Closest, 10, 1, 0.2, 0.4)
{
Body = 74;
BaseSoundID = 422;
SetStr(55);
SetDex(40);
SetInt(60);
SetDamage(10, 14);
SetDamageType(ResistanceType.Physical, 0);
SetDamageType(ResistanceType.Fire, 50);
SetDamageType(ResistanceType.Poison, 50);
SetResistance(ResistanceType.Physical, 25, 35);
SetResistance(ResistanceType.Fire, 40, 50);
SetResistance(ResistanceType.Cold, 20, 30);
SetResistance(ResistanceType.Poison, 30, 40);
SetResistance(ResistanceType.Energy, 30, 40);
SetSkill(SkillName.EvalInt, 20.1, 30.0);
SetSkill(SkillName.Magery, 60.1, 70.0);
SetSkill(SkillName.MagicResist, 30.1, 50.0);
SetSkill(SkillName.Tactics, 42.1, 50.0);
SetSkill(SkillName.Wrestling, 40.1, 44.0);
Fame = 0;
Karma = 0;
ControlSlots = 1;
}
public ArcaneFiend(Serial serial) : base(serial)
{
}
public override string CorpseName => "an imp corpse";
public override double DispelDifficulty => 70.0;
public override double DispelFocus => 20.0;
public override PackInstinct PackInstinct => PackInstinct.Daemon;
public override bool BleedImmune => true; //TODO: Verify on OSI. Guide says this.
public override string DefaultName => "an imp";
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,85 @@
using System;
namespace Server.Mobiles
{
public class NatureFury : BaseCreature
{
[Constructible]
public NatureFury()
: base(AIType.AI_Melee, FightMode.Closest, 10, 1, 0.2, 0.4)
{
Body = 0x33;
Hue = 0x4001;
SetStr(150);
SetDex(150);
SetInt(100);
SetHits(80);
SetStam(250);
SetMana(0);
SetDamage(6, 8);
SetDamageType(ResistanceType.Poison, 100);
SetDamageType(ResistanceType.Physical, 0);
SetResistance(ResistanceType.Physical, 90);
SetSkill(SkillName.Wrestling, 90.0);
SetSkill(SkillName.MagicResist, 70.0);
SetSkill(SkillName.Tactics, 100.0);
Fame = 0;
Karma = 0;
ControlSlots = 1;
}
public NatureFury(Serial serial)
: base(serial)
{
}
public override bool DeleteCorpseOnDeath => Core.AOS;
public override bool IsHouseSummonable => true;
public override double DispelDifficulty => 125.0;
public override double DispelFocus => 90.0;
public override bool BleedImmune => true;
public override Poison PoisonImmune => Poison.Lethal;
public override bool AlwaysMurderer => true;
public override string DefaultName => "a nature's fury";
public override void MoveToWorld(Point3D loc, Map map)
{
base.MoveToWorld(loc, map);
Timer.DelayCall(TimeSpan.Zero, DoEffects);
}
public void DoEffects()
{
FixedParticles(0x91C, 10, 180, 0x2543, 0, 0, EffectLayer.Waist);
PlaySound(0xE);
PlaySound(0x1BC);
if (Alive && !Deleted)
Timer.DelayCall(TimeSpan.FromSeconds(7.0), DoEffects);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
Delete();
}
}
}

View file

@ -0,0 +1,97 @@
using System;
using Server.Mobiles;
using Server.Regions;
using Server.Targeting;
namespace Server.Spells.Spellweaving
{
public class NatureFurySpell : ArcanistSpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Nature's Fury", "Rauvvrae",
-1,
false
);
public NatureFurySpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 0.0;
public override int RequiredMana => 24;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, 10);
}
public void Target(IPoint3D point)
{
Point3D p = new Point3D(point);
Map map = Caster.Map;
if (map == null)
return;
if (Region.Find(p, map).GetRegion<HouseRegion>()?.House?.IsFriend(Caster) == false)
return;
if (!map.CanSpawnMobile(p.X, p.Y, p.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
TimeSpan duration = TimeSpan.FromSeconds(Caster.Skills.Spellweaving.Value / 24 + 25 + FocusLevel * 2);
NatureFury nf = new NatureFury();
BaseCreature.Summon(nf, false, Caster, p, 0x5CB, duration);
new InternalTimer(nf).Start();
}
FinishSequence();
}
private class InternalTimer : Timer
{
private NatureFury m_NatureFury;
public InternalTimer(NatureFury nf)
: base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(5.0))
{
m_NatureFury = nf;
}
protected override void OnTick()
{
if (m_NatureFury.Deleted || !m_NatureFury.Alive || m_NatureFury.DamageMin > 20)
{
Stop();
}
else
{
++m_NatureFury.DamageMin;
++m_NatureFury.DamageMax;
}
}
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using Server.Network;
namespace Server.Spells.Spellweaving
{
public class ReaperFormSpell : ArcaneForm
{
private static SpellInfo m_Info = new SpellInfo("Reaper Form", "Tarisstree", -1);
public ReaperFormSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.5);
public override double RequiredSkill => 24.0;
public override int RequiredMana => 34;
public override int Body => 0x11D;
public override int FireResistOffset => -25;
public override int PhysResistOffset => 5 + FocusLevel;
public override int ColdResistOffset => 5 + FocusLevel;
public override int PoisResistOffset => 5 + FocusLevel;
public override int NrgyResistOffset => 5 + FocusLevel;
public virtual int SwingSpeedBonus => 10 + FocusLevel;
public virtual int SpellDamageBonus => 10 + FocusLevel;
public static void Initialize()
{
EventSink.Login += OnLogin;
}
public static void OnLogin(LoginEventArgs e)
{
TransformContext context = TransformationSpellHelper.GetContext(e.Mobile);
if (context?.Type == typeof(ReaperFormSpell))
e.Mobile.Send(SpeedControl.WalkSpeed);
}
public override void DoEffect(Mobile m)
{
m.PlaySound(0x1BA);
m.Send(SpeedControl.WalkSpeed);
}
public override void RemoveEffect(Mobile m)
{
m.Send(SpeedControl.Disable);
}
}
}

View file

@ -0,0 +1,46 @@
using System;
using Server.Engines.MLQuests;
using Server.Mobiles;
namespace Server.Spells.Spellweaving
{
public class SummonFeySpell : ArcaneSummon<ArcaneFey>
{
private static SpellInfo m_Info = new SpellInfo(
"Summon Fey", "Alalithra",
-1
);
public SummonFeySpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 38.0;
public override int RequiredMana => 10;
public override int Sound => 0x217;
public override bool CheckSequence()
{
Mobile caster = Caster;
// This is done after casting completes
if (caster is PlayerMobile mobile)
{
MLQuestContext context = MLQuestSystem.GetContext(mobile);
if (context == null || !context.SummonFey)
{
mobile.SendLocalizedMessage(
1074563); // You haven't forged a friendship with the fey and are unable to summon their aid.
return false;
}
}
return base.CheckSequence();
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using Server.Engines.MLQuests;
using Server.Mobiles;
namespace Server.Spells.Spellweaving
{
public class SummonFiendSpell : ArcaneSummon<ArcaneFiend>
{
private static SpellInfo m_Info = new SpellInfo(
"Summon Fiend", "Nylisstra",
-1
);
public SummonFiendSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 38.0;
public override int RequiredMana => 10;
public override int Sound => 0x216;
public override bool CheckSequence()
{
Mobile caster = Caster;
// This is done after casting completes
if (caster is PlayerMobile mobile)
{
MLQuestContext context = MLQuestSystem.GetContext(mobile);
if (context == null || !context.SummonFiend)
{
mobile.SendLocalizedMessage(1074564); // You haven't demonstrated mastery to summon a fiend.
return false;
}
}
return base.CheckSequence();
}
}
}

View file

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
namespace Server.Spells.Spellweaving
{
public class ThunderstormSpell : ArcanistSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Thunderstorm", "Erelonia",
-1
);
private static Dictionary<Mobile, Timer> m_Table = new Dictionary<Mobile, Timer>();
public ThunderstormSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 10.0;
public override int RequiredMana => 32;
public override void OnCast()
{
if (CheckSequence())
{
Caster.PlaySound(0x5CE);
double skill = Caster.Skills.Spellweaving.Value;
int damage = Math.Max(11, 10 + (int)(skill / 24)) + FocusLevel;
int sdiBonus = AosAttributes.GetValue(Caster, AosAttribute.SpellDamage);
int pvmDamage = damage * (100 + sdiBonus);
pvmDamage /= 100;
if (sdiBonus > 15)
sdiBonus = 15;
int pvpDamage = damage * (100 + sdiBonus);
pvpDamage /= 100;
int range = 2 + FocusLevel;
TimeSpan duration = TimeSpan.FromSeconds(5 + FocusLevel);
IPooledEnumerable<Mobile> eable = Caster.GetMobilesInRange(range);
foreach (Mobile m in eable)
{
if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanBeHarmful(m, false) ||
!Caster.InLOS(m))
continue;
Caster.DoHarmful(m);
Spell oldSpell = m.Spell as Spell;
SpellHelper.Damage(this, m, m.Player && Caster.Player ? pvpDamage : pvmDamage, 0, 0, 0, 0, 100);
if (oldSpell == null || oldSpell == m.Spell || CheckResisted(m))
continue;
m_Table[m] = Timer.DelayCall(duration, DoExpire, m);
BuffInfo.AddBuff(m,
new BuffInfo(BuffIcon.Thunderstorm, 1075800, duration, m, GetCastRecoveryMalus(m)));
}
eable.Free();
}
FinishSequence();
}
public static int GetCastRecoveryMalus(Mobile m)
{
return m_Table.ContainsKey(m) ? 6 : 0;
}
public static void DoExpire(Mobile m)
{
if (!m_Table.TryGetValue(m, out Timer t))
return;
t.Stop();
m_Table.Remove(m);
BuffInfo.RemoveBuff(m, BuffIcon.Thunderstorm);
}
}
}

View file

@ -0,0 +1,68 @@
using System;
using Server.Targeting;
namespace Server.Spells.Spellweaving
{
public class WordOfDeathSpell : ArcanistSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo("Word of Death", "Nyraxle", -1);
public WordOfDeathSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(3.5);
public override double RequiredSkill => 80.0;
public override int RequiredMana => 50;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, 10);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (CheckHSequence(m))
{
Point3D loc = m.Location;
loc.Z += 50;
m.PlaySound(0x211);
m.FixedParticles(0x3779, 1, 30, 0x26EC, 0x3, 0x3, EffectLayer.Waist);
Effects.SendMovingParticles(new Entity(Serial.Zero, loc, m.Map), new Entity(Serial.Zero, m.Location, m.Map),
0xF5F, 1, 0, true, false, 0x21, 0x3F, 0x251D, 0, 0, EffectLayer.Head, 0);
double percentage = 0.05 * FocusLevel;
int damage;
if (!m.Player && m.Hits / (double)m.HitsMax < percentage)
{
damage = 300;
}
else
{
int minDamage = (int)Caster.Skills.Spellweaving.Value / 5;
int maxDamage = (int)Caster.Skills.Spellweaving.Value / 3;
damage = Utility.RandomMinMax(minDamage, maxDamage);
int damageBonus = AosAttributes.GetValue(Caster, AosAttribute.SpellDamage);
if (m.Player && damageBonus > 15)
damageBonus = 15;
damage *= damageBonus + 100;
damage /= 100;
}
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 0, 100);
}
FinishSequence();
}
}
}