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,12 @@
namespace Server.Spells
{
public enum DisturbType
{
Unspecified,
EquipRequest,
UseRequest,
Hurt,
Kill,
NewCast
}
}

View file

@ -0,0 +1,102 @@
using System;
using Server.Items;
namespace Server.Spells
{
public abstract class MagerySpell : Spell
{
private const double ChanceOffset = 20.0, ChanceLength = 100.0 / 7.0;
private static int[] m_ManaTable = { 4, 6, 9, 11, 14, 20, 40, 50 };
public MagerySpell(Mobile caster, Item scroll, SpellInfo info)
: base(caster, scroll, info)
{
}
public abstract SpellCircle Circle{ get; }
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds((3 + (int)Circle) * CastDelaySecondsPerTick);
public override bool ConsumeReagents()
{
return base.ConsumeReagents() || ArcaneGem.ConsumeCharges(Caster, Core.SE ? 1 : 1 + (int)Circle);
}
public override void GetCastSkills(out double min, out double max)
{
int circle = (int)Circle;
if (Scroll != null)
circle -= 2;
double avg = ChanceLength * circle;
min = avg - ChanceOffset;
max = avg + ChanceOffset;
}
public override int GetMana()
{
return Scroll is BaseWand ? 0 : m_ManaTable[(int)Circle];
}
public override double GetResistSkill(Mobile m)
{
int maxSkill = (1 + (int)Circle) * 10;
maxSkill += (1 + (int)Circle / 6) * 25;
if (m.Skills.MagicResist.Value < maxSkill)
m.CheckSkill(SkillName.MagicResist, 0.0, m.Skills.MagicResist.Cap);
return m.Skills.MagicResist.Value;
}
public virtual bool CheckResisted(Mobile target)
{
double n = GetResistPercent(target);
n /= 100.0;
if (n <= 0.0)
return false;
if (n >= 1.0)
return true;
int maxSkill = (1 + (int)Circle) * 10;
maxSkill += (1 + (int)Circle / 6) * 25;
if (target.Skills.MagicResist.Value < maxSkill)
target.CheckSkill(SkillName.MagicResist, 0.0, target.Skills.MagicResist.Cap);
return n >= Utility.RandomDouble();
}
public virtual double GetResistPercentForCircle(Mobile target, SpellCircle circle)
{
double firstPercent = target.Skills.MagicResist.Value / 5.0;
double secondPercent = target.Skills.MagicResist.Value -
((Caster.Skills[CastSkill].Value - 20.0) / 5.0 + (1 + (int)circle) * 5.0);
return (firstPercent > secondPercent ? firstPercent : secondPercent) /
2.0; // Seems should be about half of what stratics says.
}
public virtual double GetResistPercent(Mobile target)
{
return GetResistPercentForCircle(target, Circle);
}
public override TimeSpan GetCastDelay()
{
if (!Core.ML && Scroll is BaseWand)
return TimeSpan.Zero;
if (!Core.AOS)
return TimeSpan.FromSeconds(0.5 + 0.25 * (int)Circle);
return base.GetCastDelay();
}
}
}

View file

@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using Server.Engines.ConPVP;
using Server.Items;
using Server.Network;
using Server.Spells.Bushido;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
namespace Server.Spells
{
public abstract class SpecialMove
{
private static Dictionary<Mobile, SpecialMoveContext> m_PlayersTable = new Dictionary<Mobile, SpecialMoveContext>();
public virtual int BaseMana => 0;
public virtual SkillName MoveSkill => SkillName.Bushido;
public virtual double RequiredSkill => 0.0;
public virtual TextDefinition AbilityMessage => 0;
public virtual bool BlockedByAnimalForm => true;
public virtual bool DelayedContext => false;
public static Dictionary<Mobile, SpecialMove> Table{ get; } = new Dictionary<Mobile, SpecialMove>();
public virtual bool ValidatesDuringHit => true;
public virtual int GetAccuracyBonus(Mobile attacker)
{
return 0;
}
public virtual double GetDamageScalar(Mobile attacker, Mobile defender)
{
return 1.0;
}
// Called before swinging, to make sure the accuracy scalar is to be computed.
public virtual bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
return true;
}
// Called when a hit connects, but before damage is calculated.
public virtual bool OnBeforeDamage(Mobile attacker, Mobile defender)
{
return true;
}
// Called as soon as the ability is used.
public virtual void OnUse(Mobile from)
{
}
// Called when a hit connects, at the end of the weapon.OnHit() method.
public virtual void OnHit(Mobile attacker, Mobile defender, int damage)
{
}
// Called when a hit misses.
public virtual void OnMiss(Mobile attacker, Mobile defender)
{
}
// Called when the move is cleared.
public virtual void OnClearMove(Mobile from)
{
}
public virtual bool IgnoreArmor(Mobile attacker)
{
return false;
}
public virtual double GetPropertyBonus(Mobile attacker)
{
return 1.0;
}
public virtual bool CheckSkills(Mobile m)
{
if (m.Skills[MoveSkill].Value < RequiredSkill)
{
string args = $"{RequiredSkill.ToString("F1")}\t{MoveSkill.ToString()}\t ";
m.SendLocalizedMessage(1063013,
args); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return false;
}
return true;
}
public virtual int ScaleMana(Mobile m, int mana)
{
double scalar = 1.0;
if (!MindRotSpell.GetMindRotScalar(m, ref scalar))
scalar = 1.0;
// Lower Mana Cost = 40%
int lmc = Math.Min(AosAttributes.GetValue(m, AosAttribute.LowerManaCost), 40);
scalar -= (double)lmc / 100;
int total = (int)(mana * scalar);
if (m.Skills[MoveSkill].Value < 50.0 && GetContext(m) != null)
total *= 2;
return total;
}
public virtual bool CheckMana(Mobile from, bool consume)
{
int mana = ScaleMana(from, BaseMana);
if (from.Mana < mana)
{
from.SendLocalizedMessage(1060181,
mana.ToString()); // You need ~1_MANA_REQUIREMENT~ mana to perform that attack
return false;
}
if (consume)
{
if (!DelayedContext)
SetContext(from);
from.Mana -= mana;
}
return true;
}
public virtual void SetContext(Mobile from)
{
if (GetContext(from) == null)
if (DelayedContext || from.Skills[MoveSkill].Value < 50.0)
{
Timer timer = new SpecialMoveTimer(from);
timer.Start();
AddContext(from, new SpecialMoveContext(timer, GetType()));
}
}
public virtual bool Validate(Mobile from)
{
if (!from.Player)
return true;
if (HonorableExecution.IsUnderPenalty(from))
{
from.SendLocalizedMessage(1063024); // You cannot perform this special move right now.
return false;
}
if (AnimalForm.UnderTransformation(from))
{
from.SendLocalizedMessage(1063024); // You cannot perform this special move right now.
return false;
}
#region Dueling
string option = null;
if (this is Backstab)
option = "Backstab";
else if (this is DeathStrike)
option = "Death Strike";
else if (this is FocusAttack)
option = "Focus Attack";
else if (this is KiAttack)
option = "Ki Attack";
else if (this is SurpriseAttack)
option = "Surprise Attack";
else if (this is HonorableExecution)
option = "Honorable Execution";
else if (this is LightningStrike)
option = "Lightning Strike";
else if (this is MomentumStrike)
option = "Momentum Strike";
if (option != null && !DuelContext.AllowSpecialMove(from, option, this))
return false;
#endregion
return CheckSkills(from) && CheckMana(from, false);
}
public virtual void CheckGain(Mobile m)
{
m.CheckSkill(MoveSkill, RequiredSkill, RequiredSkill + 37.5);
}
public static void ClearAllMoves(Mobile m)
{
foreach (KeyValuePair<int, SpecialMove> kvp in SpellRegistry.SpecialMoves)
{
int moveID = kvp.Key;
if (moveID != -1)
m.Send(new ToggleSpecialAbility(moveID + 1, false));
}
}
public static SpecialMove GetCurrentMove(Mobile m)
{
if (m == null)
return null;
if (!Core.SE)
{
ClearCurrentMove(m);
return null;
}
if (Table.TryGetValue(m, out SpecialMove move) && move.ValidatesDuringHit && !move.Validate(m))
{
ClearCurrentMove(m);
return null;
}
return move;
}
public static bool SetCurrentMove(Mobile m, SpecialMove move)
{
if (!Core.SE)
{
ClearCurrentMove(m);
return false;
}
if (move?.Validate(m) == false)
{
ClearCurrentMove(m);
return false;
}
bool sameMove = move == GetCurrentMove(m);
ClearCurrentMove(m);
if (sameMove)
return true;
if (move != null)
{
WeaponAbility.ClearCurrentAbility(m);
Table[m] = move;
move.OnUse(m);
int moveID = SpellRegistry.GetRegistryNumber(move);
if (moveID > 0)
m.Send(new ToggleSpecialAbility(moveID + 1, true));
TextDefinition.SendMessageTo(m, move.AbilityMessage);
}
return true;
}
public static void ClearCurrentMove(Mobile m)
{
;
if (Table.TryGetValue(m, out SpecialMove move))
{
move.OnClearMove(m);
int moveID = SpellRegistry.GetRegistryNumber(move);
if (moveID > 0)
m.Send(new ToggleSpecialAbility(moveID + 1, false));
}
Table.Remove(m);
}
private static void AddContext(Mobile m, SpecialMoveContext context)
{
m_PlayersTable[m] = context;
}
private static void RemoveContext(Mobile m)
{
SpecialMoveContext context = GetContext(m);
if (context != null)
{
m_PlayersTable.Remove(m);
context.Timer.Stop();
}
}
private static SpecialMoveContext GetContext(Mobile m)
{
return m_PlayersTable.TryGetValue(m, out SpecialMoveContext context) ? context : null;
}
private class SpecialMoveTimer : Timer
{
private Mobile m_Mobile;
public SpecialMoveTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0))
{
m_Mobile = from;
Priority = TimerPriority.TwentyFiveMS;
}
protected override void OnTick()
{
RemoveContext(m_Mobile);
}
}
public class SpecialMoveContext
{
public SpecialMoveContext(Timer timer, Type type)
{
Timer = timer;
Type = type;
}
public Timer Timer{ get; }
public Type Type{ get; }
}
}
}

View file

@ -0,0 +1,898 @@
using System;
using System.Collections.Generic;
using Server.Engines.ConPVP;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
using Server.Network;
using Server.Spells.Bushido;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
using Server.Spells.Second;
using Server.Spells.Spellweaving;
using Server.Targeting;
namespace Server.Spells
{
public abstract class Spell : ISpell
{
private static TimeSpan NextSpellDelay = TimeSpan.FromSeconds(0.75);
private static TimeSpan AnimateDelay = TimeSpan.FromSeconds(1.5);
//In reality, it's ANY delayed Damage spell Post-AoS that can't stack, but, only
//Expo & Magic Arrow have enough delay and a short enough cast time to bring up
//the possibility of stacking 'em. Note that a MA & an Explosion will stack, but
//of course, two MA's won't.
private static Dictionary<Type, DelayedDamageContextWrapper> m_ContextTable =
new Dictionary<Type, DelayedDamageContextWrapper>();
private AnimTimer m_AnimTimer;
private CastTimer m_CastTimer;
public Spell(Mobile caster, Item scroll, SpellInfo info)
{
Caster = caster;
Scroll = scroll;
Info = info;
}
public SpellState State{ get; set; }
public Mobile Caster{ get; }
public SpellInfo Info{ get; }
public string Name => Info.Name;
public string Mantra => Info.Mantra;
public Type[] Reagents => Info.Reagents;
public Item Scroll{ get; }
public long StartCastTime{ get; private set; }
public virtual SkillName CastSkill => SkillName.Magery;
public virtual SkillName DamageSkill => SkillName.EvalInt;
public virtual bool RevealOnCast => true;
public virtual bool ClearHandsOnCast => true;
public virtual bool ShowHandMovement => true;
public virtual bool DelayedDamage => false;
public virtual bool DelayedDamageStacking => true;
public virtual bool BlockedByHorrificBeast => true;
public virtual bool BlockedByAnimalForm => true;
public virtual bool BlocksMovement => true;
public virtual bool CheckNextSpellTime => !(Scroll is BaseWand);
public virtual int CastRecoveryBase => 6;
public virtual int CastRecoveryFastScalar => 1;
public virtual int CastRecoveryPerSecond => 4;
public virtual int CastRecoveryMinimum => 0;
public abstract TimeSpan CastDelayBase{ get; }
public virtual double CastDelayFastScalar => 1;
public virtual double CastDelaySecondsPerTick => 0.25;
public virtual TimeSpan CastDelayMinimum => TimeSpan.FromSeconds(0.25);
public virtual bool IsCasting => State == SpellState.Casting;
public virtual void OnCasterHurt()
{
//Confirm: Monsters and pets cannot be disturbed.
if (Caster.Player && IsCasting && ProtectionSpell.Registry.TryGetValue(Caster, out double d) &&
d <= Utility.RandomDouble() * 100.0)
Disturb(DisturbType.Hurt, false, true);
}
public virtual void OnCasterKilled()
{
Disturb(DisturbType.Kill);
}
public virtual void OnConnectionChanged()
{
FinishSequence();
}
public virtual bool OnCasterMoving(Direction d)
{
if (IsCasting && BlocksMovement)
{
Caster.SendLocalizedMessage(500111); // You are frozen and can not move.
return false;
}
return true;
}
public virtual bool OnCasterEquipping(Item item)
{
if (IsCasting)
Disturb(DisturbType.EquipRequest);
return true;
}
public virtual bool OnCasterUsingObject(IEntity entity)
{
if (State == SpellState.Sequencing)
Disturb(DisturbType.UseRequest);
return true;
}
public virtual bool OnCastInTown(Region r)
{
return Info.AllowTown;
}
public void StartDelayedDamageContext(Mobile m, Timer t)
{
if (DelayedDamageStacking)
return; //Sanity
if (!m_ContextTable.TryGetValue(GetType(), out DelayedDamageContextWrapper contexts))
m_ContextTable[GetType()] = contexts = new DelayedDamageContextWrapper();
contexts.Add(m, t);
}
public void RemoveDelayedDamageContext(Mobile m)
{
if (m_ContextTable.TryGetValue(GetType(), out DelayedDamageContextWrapper contexts))
contexts.Remove(m);
}
public void HarmfulSpell(Mobile m)
{
(m as BaseCreature)?.OnHarmfulSpell(Caster);
}
public virtual int GetNewAosDamage(int bonus, int dice, int sides, Mobile singleTarget)
{
if (singleTarget != null)
return GetNewAosDamage(bonus, dice, sides, Caster.Player && singleTarget.Player,
GetDamageScalar(singleTarget));
return GetNewAosDamage(bonus, dice, sides, false);
}
public virtual int GetNewAosDamage(int bonus, int dice, int sides, bool playerVsPlayer)
{
return GetNewAosDamage(bonus, dice, sides, playerVsPlayer, 1.0);
}
public virtual int GetNewAosDamage(int bonus, int dice, int sides, bool playerVsPlayer, double scalar)
{
int damage = Utility.Dice(dice, sides, bonus) * 100;
int damageBonus = 0;
int inscribeSkill = GetInscribeFixed(Caster);
int inscribeBonus = (inscribeSkill + 1000 * (inscribeSkill / 1000)) / 200;
damageBonus += inscribeBonus;
int intBonus = Caster.Int / 10;
damageBonus += intBonus;
int sdiBonus = AosAttributes.GetValue(Caster, AosAttribute.SpellDamage);
// PvP spell damage increase cap of 15% from an item<65>s magic property
if (playerVsPlayer && sdiBonus > 15)
sdiBonus = 15;
damageBonus += sdiBonus;
TransformContext context = TransformationSpellHelper.GetContext(Caster);
if (context?.Spell is ReaperFormSpell spell)
damageBonus += spell.SpellDamageBonus;
damage = AOS.Scale(damage, 100 + damageBonus);
int evalSkill = GetDamageFixed(Caster);
int evalScale = 30 + 9 * evalSkill / 100;
damage = AOS.Scale(damage, evalScale);
damage = AOS.Scale(damage, (int)(scalar * 100));
return damage / 100;
}
public virtual bool ConsumeReagents()
{
if (Scroll != null || !Caster.Player)
return true;
if (AosAttributes.GetValue(Caster, AosAttribute.LowerRegCost) > Utility.Random(100))
return true;
if (DuelContext.IsFreeConsume(Caster))
return true;
Container pack = Caster.Backpack;
if (pack == null)
return false;
if (pack.ConsumeTotal(Info.Reagents, Info.Amounts) == -1)
return true;
return false;
}
public virtual double GetInscribeSkill(Mobile m)
{
// There is no chance to gain
// m.CheckSkill( SkillName.Inscribe, 0.0, 120.0 );
return m.Skills.Inscribe.Value;
}
public virtual int GetInscribeFixed(Mobile m)
{
// There is no chance to gain
// m.CheckSkill( SkillName.Inscribe, 0.0, 120.0 );
return m.Skills.Inscribe.Fixed;
}
public virtual int GetDamageFixed(Mobile m)
{
//m.CheckSkill( DamageSkill, 0.0, m.Skills[DamageSkill].Cap );
return m.Skills[DamageSkill].Fixed;
}
public virtual double GetDamageSkill(Mobile m)
{
//m.CheckSkill( DamageSkill, 0.0, m.Skills[DamageSkill].Cap );
return m.Skills[DamageSkill].Value;
}
public virtual double GetResistSkill(Mobile m)
{
return m.Skills.MagicResist.Value;
}
public virtual double GetDamageScalar(Mobile target)
{
double scalar = 1.0;
if (!Core.AOS) //EvalInt stuff for AoS is handled elsewhere
{
double casterEI = Caster.Skills[DamageSkill].Value;
double targetRS = target.Skills.MagicResist.Value;
/*
if ( Core.AOS )
targetRS = 0;
*/
//m_Caster.CheckSkill( DamageSkill, 0.0, 120.0 );
if (casterEI > targetRS)
scalar = 1.0 + (casterEI - targetRS) / 500.0;
else
scalar = 1.0 + (casterEI - targetRS) / 200.0;
// magery damage bonus, -25% at 0 skill, +0% at 100 skill, +5% at 120 skill
scalar += (Caster.Skills[CastSkill].Value - 100.0) / 400.0;
if (!target.Player && !target.Body.IsHuman /*&& !Core.AOS*/)
scalar *= 2.0; // Double magery damage to monsters/animals if not AOS
}
(target as BaseCreature)?.AlterDamageScalarFrom(Caster, ref scalar);
(Caster as BaseCreature)?.AlterDamageScalarTo(target, ref scalar);
if (Core.SE)
scalar *= GetSlayerDamageScalar(target);
target.Region.SpellDamageScalar(Caster, target, ref scalar);
if (Evasion.CheckSpellEvasion(target)) //Only single target spells an be evaded
scalar = 0;
return scalar;
}
public virtual double GetSlayerDamageScalar(Mobile defender)
{
Spellbook atkBook = Spellbook.FindEquippedSpellbook(Caster);
double scalar = 1.0;
if (atkBook != null)
{
SlayerEntry atkSlayer = SlayerGroup.GetEntryByName(atkBook.Slayer);
SlayerEntry atkSlayer2 = SlayerGroup.GetEntryByName(atkBook.Slayer2);
if (atkSlayer?.Slays(defender) == true || atkSlayer2?.Slays(defender) == true)
{
defender.FixedEffect(0x37B9, 10, 5); //TODO: Confirm this displays on OSIs
scalar = 2.0;
}
TransformContext context = TransformationSpellHelper.GetContext(defender);
if ((atkBook.Slayer == SlayerName.Silver || atkBook.Slayer2 == SlayerName.Silver) && context != null &&
context.Type != typeof(HorrificBeastSpell))
scalar += .25; // Every necromancer transformation other than horrific beast take an additional 25% damage
if (scalar != 1.0)
return scalar;
}
ISlayer defISlayer = Spellbook.FindEquippedSpellbook(defender) ?? defender.Weapon as ISlayer;
if (defISlayer != null)
{
SlayerEntry defSlayer = SlayerGroup.GetEntryByName(defISlayer.Slayer);
SlayerEntry defSlayer2 = SlayerGroup.GetEntryByName(defISlayer.Slayer2);
if (defSlayer?.Group.OppositionSuperSlays(Caster) == true ||
defSlayer2?.Group.OppositionSuperSlays(Caster) == true)
scalar = 2.0;
}
return scalar;
}
public virtual void DoFizzle()
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x3B2, 502632); // The spell fizzles.
if (Caster.Player)
{
if (Core.AOS)
Caster.FixedParticles(0x3735, 1, 30, 9503, EffectLayer.Waist);
else
Caster.FixedEffect(0x3735, 6, 30);
Caster.PlaySound(0x5C);
}
}
public virtual bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable)
{
return !(resistable && Scroll is BaseWand);
}
public void Disturb(DisturbType type, bool firstCircle = true, bool resistable = false)
{
if (!CheckDisturb(type, firstCircle, resistable))
return;
if (State == SpellState.Casting)
{
if (!firstCircle && !Core.AOS && this is MagerySpell && ((MagerySpell)this).Circle == SpellCircle.First)
return;
State = SpellState.None;
Caster.Spell = null;
OnDisturb(type, true);
m_CastTimer?.Stop();
m_AnimTimer?.Stop();
if (Core.AOS && Caster.Player && type == DisturbType.Hurt)
DoHurtFizzle();
Caster.NextSpellTime = Core.TickCount + (int)GetDisturbRecovery().TotalMilliseconds;
}
else if (State == SpellState.Sequencing)
{
if (!firstCircle && !Core.AOS && this is MagerySpell && ((MagerySpell)this).Circle == SpellCircle.First)
return;
State = SpellState.None;
Caster.Spell = null;
OnDisturb(type, false);
Target.Cancel(Caster);
if (Core.AOS && Caster.Player && type == DisturbType.Hurt)
DoHurtFizzle();
}
}
public virtual void DoHurtFizzle()
{
Caster.FixedEffect(0x3735, 6, 30);
Caster.PlaySound(0x5C);
}
public virtual void OnDisturb(DisturbType type, bool message)
{
if (message)
Caster.SendLocalizedMessage(500641); // Your concentration is disturbed, thus ruining thy spell.
}
public virtual bool CheckCast()
{
return true;
}
public virtual void SayMantra()
{
if (Scroll is BaseWand)
return;
if (!string.IsNullOrEmpty(Info.Mantra) && Caster.Player)
Caster.PublicOverheadMessage(MessageType.Spell, Caster.SpeechHue, true, Info.Mantra, false);
}
public bool Cast()
{
StartCastTime = Core.TickCount;
if (Core.AOS && Caster.Spell is Spell spell && spell.State == SpellState.Sequencing)
spell.Disturb(DisturbType.NewCast);
if (!Caster.CheckAlive()) return false;
if (Scroll is BaseWand && Caster.Spell != null && Caster.Spell.IsCasting)
{
Caster.SendLocalizedMessage(502643); // You can not cast a spell while frozen.
}
else if (Caster.Spell != null && Caster.Spell.IsCasting)
{
Caster.SendLocalizedMessage(502642); // You are already casting a spell.
}
else if (BlockedByHorrificBeast &&
TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)) ||
BlockedByAnimalForm && AnimalForm.UnderTransformation(Caster))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
}
else if (!(Scroll is BaseWand) && (Caster.Paralyzed || Caster.Frozen))
{
Caster.SendLocalizedMessage(502643); // You can not cast a spell while frozen.
}
else if (CheckNextSpellTime && Core.TickCount - Caster.NextSpellTime < 0)
{
Caster.SendLocalizedMessage(502644); // You have not yet recovered from casting a spell.
}
else if (Caster is PlayerMobile mobile && mobile.PeacedUntil > DateTime.UtcNow)
{
mobile.SendLocalizedMessage(1072060); // You cannot cast a spell while calmed.
}
#region Dueling
else if ((Caster as PlayerMobile)?.DuelContext != null &&
!((PlayerMobile)Caster).DuelContext.AllowSpellCast(Caster, this))
{
}
#endregion
else if (Caster.Mana >= ScaleMana(GetMana()))
{
if (Caster.Spell == null && Caster.CheckSpellCast(this) && CheckCast() &&
Caster.Region.OnBeginSpellCast(Caster, this))
{
State = SpellState.Casting;
Caster.Spell = this;
if (!(Scroll is BaseWand) && RevealOnCast)
Caster.RevealingAction();
SayMantra();
TimeSpan castDelay = GetCastDelay();
if (ShowHandMovement && (Caster.Body.IsHuman || Caster.Player && Caster.Body.IsMonster))
{
int count = (int)Math.Ceiling(castDelay.TotalSeconds / AnimateDelay.TotalSeconds);
if (count != 0)
{
m_AnimTimer = new AnimTimer(this, count);
m_AnimTimer.Start();
}
if (Info.LeftHandEffect > 0)
Caster.FixedParticles(0, 10, 5, Info.LeftHandEffect, EffectLayer.LeftHand);
if (Info.RightHandEffect > 0)
Caster.FixedParticles(0, 10, 5, Info.RightHandEffect, EffectLayer.RightHand);
}
if (ClearHandsOnCast)
Caster.ClearHands();
if (Core.ML)
WeaponAbility.ClearCurrentAbility(Caster);
m_CastTimer = new CastTimer(this, castDelay);
//m_CastTimer.Start();
OnBeginCast();
if (castDelay > TimeSpan.Zero)
m_CastTimer.Start();
else
m_CastTimer.Tick();
return true;
}
return false;
}
else
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x22, 502625); // Insufficient mana
}
return false;
}
public abstract void OnCast();
public virtual void OnBeginCast()
{
}
public virtual void GetCastSkills(out double min, out double max)
{
min = max = 0; //Intended but not required for overriding.
}
public virtual bool CheckFizzle()
{
if (Scroll is BaseWand)
return true;
GetCastSkills(out double minSkill, out double maxSkill);
if (DamageSkill != CastSkill)
Caster.CheckSkill(DamageSkill, 0.0, Caster.Skills[DamageSkill].Cap);
return Caster.CheckSkill(CastSkill, minSkill, maxSkill);
}
public abstract int GetMana();
public virtual int ScaleMana(int mana)
{
double scalar = 1.0;
if (!MindRotSpell.GetMindRotScalar(Caster, ref scalar))
scalar = 1.0;
// Lower Mana Cost = 40%
int lmc = AosAttributes.GetValue(Caster, AosAttribute.LowerManaCost);
if (lmc > 40)
lmc = 40;
scalar -= (double)lmc / 100;
return (int)(mana * scalar);
}
public virtual TimeSpan GetDisturbRecovery()
{
if (Core.AOS)
return TimeSpan.Zero;
double delay = 1.0 - Math.Sqrt((Core.TickCount - StartCastTime) / 1000.0 / GetCastDelay().TotalSeconds);
if (delay < 0.2)
delay = 0.2;
return TimeSpan.FromSeconds(delay);
}
public virtual TimeSpan GetCastRecovery()
{
if (!Core.AOS)
return NextSpellDelay;
int fcr = AosAttributes.GetValue(Caster, AosAttribute.CastRecovery);
fcr -= ThunderstormSpell.GetCastRecoveryMalus(Caster);
int fcrDelay = -(CastRecoveryFastScalar * fcr);
int delay = CastRecoveryBase + fcrDelay;
if (delay < CastRecoveryMinimum)
delay = CastRecoveryMinimum;
return TimeSpan.FromSeconds((double)delay / CastRecoveryPerSecond);
}
//public virtual int CastDelayBase{ get{ return 3; } }
//public virtual int CastDelayFastScalar{ get{ return 1; } }
//public virtual int CastDelayPerSecond{ get{ return 4; } }
//public virtual int CastDelayMinimum{ get{ return 1; } }
public virtual TimeSpan GetCastDelay()
{
if (Scroll is BaseWand)
return Core.ML ? CastDelayBase : TimeSpan.Zero; // TODO: Should FC apply to wands?
// Faster casting cap of 2 (if not using the protection spell)
// Faster casting cap of 0 (if using the protection spell)
// Paladin spells are subject to a faster casting cap of 4
// Paladins with magery of 70.0 or above are subject to a faster casting cap of 2
int fcMax = 4;
if (CastSkill == SkillName.Magery || CastSkill == SkillName.Necromancy ||
CastSkill == SkillName.Chivalry && Caster.Skills.Magery.Value >= 70.0)
fcMax = 2;
int fc = AosAttributes.GetValue(Caster, AosAttribute.CastSpeed);
if (fc > fcMax)
fc = fcMax;
if (ProtectionSpell.Registry.ContainsKey(Caster))
fc -= 2;
if (EssenceOfWindSpell.IsDebuffed(Caster))
fc -= EssenceOfWindSpell.GetFCMalus(Caster);
TimeSpan baseDelay = CastDelayBase;
TimeSpan fcDelay = TimeSpan.FromSeconds(-(CastDelayFastScalar * fc * CastDelaySecondsPerTick));
//int delay = CastDelayBase + circleDelay + fcDelay;
TimeSpan delay = baseDelay + fcDelay;
if (delay < CastDelayMinimum)
delay = CastDelayMinimum;
//return TimeSpan.FromSeconds( (double)delay / CastDelayPerSecond );
return delay;
}
public virtual void FinishSequence()
{
State = SpellState.None;
if (Caster.Spell == this)
Caster.Spell = null;
}
public virtual int ComputeKarmaAward()
{
return 0;
}
public virtual bool CheckSequence()
{
int mana = ScaleMana(GetMana());
if (Caster.Deleted || !Caster.Alive || Caster.Spell != this || State != SpellState.Sequencing)
{
DoFizzle();
}
else if (Scroll != null && !(Scroll is Runebook) &&
(Scroll.Amount <= 0 || Scroll.Deleted || Scroll.RootParent != Caster || Scroll is BaseWand baseWand &&
(baseWand.Charges <= 0 || baseWand.Parent != Caster)))
{
DoFizzle();
}
else if (!ConsumeReagents())
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x22, 502630); // More reagents are needed for this spell.
}
else if (Caster.Mana < mana)
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x22, 502625); // Insufficient mana for this spell.
}
else if (Core.AOS && (Caster.Frozen || Caster.Paralyzed))
{
Caster.SendLocalizedMessage(502646); // You cannot cast a spell while frozen.
DoFizzle();
}
else if (Caster is PlayerMobile mobile && mobile.PeacedUntil > DateTime.UtcNow)
{
mobile.SendLocalizedMessage(1072060); // You cannot cast a spell while calmed.
DoFizzle();
}
else if (CheckFizzle())
{
Caster.Mana -= mana;
if (Scroll is SpellScroll)
{
Scroll.Consume();
}
else if (Scroll is BaseWand wand)
{
wand.ConsumeCharge(Caster);
Caster.RevealingAction();
}
if (Scroll is BaseWand)
{
bool m = Scroll.Movable;
Scroll.Movable = false;
if (ClearHandsOnCast)
Caster.ClearHands();
Scroll.Movable = m;
}
else
{
if (ClearHandsOnCast)
Caster.ClearHands();
}
int karma = ComputeKarmaAward();
if (karma != 0)
Titles.AwardKarma(Caster, karma, true);
if (TransformationSpellHelper.UnderTransformation(Caster, typeof(VampiricEmbraceSpell)))
{
bool garlic = false;
for (int i = 0; !garlic && i < Info.Reagents.Length; ++i)
garlic = Info.Reagents[i] == Reagent.Garlic;
if (garlic)
{
Caster.SendLocalizedMessage(1061651); // The garlic burns you!
AOS.Damage(Caster, Utility.RandomMinMax(17, 23), 100, 0, 0, 0, 0);
}
}
return true;
}
else
{
DoFizzle();
}
return false;
}
public bool CheckBSequence(Mobile target)
{
return CheckBSequence(target, false);
}
public bool CheckBSequence(Mobile target, bool allowDead)
{
if (!target.Alive && !allowDead)
{
Caster.SendLocalizedMessage(501857); // This spell won't work on that!
return false;
}
if (Caster.CanBeBeneficial(target, true, allowDead) && CheckSequence())
{
Caster.DoBeneficial(target);
return true;
}
return false;
}
public bool CheckHSequence(Mobile target)
{
if (!target.Alive)
{
Caster.SendLocalizedMessage(501857); // This spell won't work on that!
return false;
}
if (Caster.CanBeHarmful(target) && CheckSequence())
{
Caster.DoHarmful(target);
return true;
}
return false;
}
private class DelayedDamageContextWrapper
{
private Dictionary<Mobile, Timer> m_Contexts = new Dictionary<Mobile, Timer>();
public void Add(Mobile m, Timer t)
{
if (m_Contexts.TryGetValue(m, out Timer oldTimer))
{
oldTimer.Stop();
m_Contexts.Remove(m);
}
m_Contexts.Add(m, t);
}
public void Remove(Mobile m)
{
m_Contexts.Remove(m);
}
}
private class AnimTimer : Timer
{
private Spell m_Spell;
public AnimTimer(Spell spell, int count) : base(TimeSpan.Zero, AnimateDelay, count)
{
m_Spell = spell;
Priority = TimerPriority.FiftyMS;
}
protected override void OnTick()
{
if (m_Spell.State != SpellState.Casting || m_Spell.Caster.Spell != m_Spell)
{
Stop();
return;
}
if (!m_Spell.Caster.Mounted && m_Spell.Info.Action >= 0)
{
if (m_Spell.Caster.Body.IsHuman)
m_Spell.Caster.Animate(m_Spell.Info.Action, 7, 1, true, false, 0);
else if (m_Spell.Caster.Player && m_Spell.Caster.Body.IsMonster)
m_Spell.Caster.Animate(12, 7, 1, true, false, 0);
}
if (!Running)
m_Spell.m_AnimTimer = null;
}
}
private class CastTimer : Timer
{
private Spell m_Spell;
public CastTimer(Spell spell, TimeSpan castDelay) : base(castDelay)
{
m_Spell = spell;
Priority = TimerPriority.TwentyFiveMS;
}
protected override void OnTick()
{
if (m_Spell?.Caster == null) return;
if (m_Spell.State == SpellState.Casting && m_Spell.Caster.Spell == m_Spell)
{
m_Spell.State = SpellState.Sequencing;
m_Spell.m_CastTimer = null;
m_Spell.Caster.OnSpellCast(m_Spell);
m_Spell.Caster.Region?.OnSpellCast(m_Spell.Caster, m_Spell);
m_Spell.Caster.NextSpellTime =
Core.TickCount + (int)m_Spell.GetCastRecovery().TotalMilliseconds; // Spell.NextSpellDelay;
Target originalTarget = m_Spell.Caster.Target;
m_Spell.OnCast();
if (m_Spell.Caster.Player && m_Spell.Caster.Target != originalTarget)
m_Spell.Caster.Target?.BeginTimeout(m_Spell.Caster, TimeSpan.FromSeconds(30.0));
m_Spell.m_CastTimer = null;
}
}
public void Tick()
{
OnTick();
}
}
}
}

View file

@ -0,0 +1,14 @@
namespace Server.Spells
{
public enum SpellCircle
{
First,
Second,
Third,
Fourth,
Fifth,
Sixth,
Seventh,
Eighth
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
using System;
namespace Server.Spells
{
public class SpellInfo
{
public SpellInfo(string name, string mantra, params Type[] regs) : this(name, mantra, 16, 0, 0, true, regs)
{
}
public SpellInfo(string name, string mantra, bool allowTown, params Type[] regs) : this(name, mantra, 16, 0, 0,
allowTown, regs)
{
}
public SpellInfo(string name, string mantra, int action, params Type[] regs) : this(name, mantra, action, 0, 0, true,
regs)
{
}
public SpellInfo(string name, string mantra, int action, bool allowTown, params Type[] regs) : this(name, mantra,
action, 0, 0, allowTown, regs)
{
}
public SpellInfo(string name, string mantra, int action, int handEffect, params Type[] regs) : this(name, mantra,
action, handEffect, handEffect, true, regs)
{
}
public SpellInfo(string name, string mantra, int action, int handEffect, bool allowTown, params Type[] regs) : this(
name, mantra, action, handEffect, handEffect, allowTown, regs)
{
}
public SpellInfo(string name, string mantra, int action, int leftHandEffect, int rightHandEffect, bool allowTown,
params Type[] regs)
{
Name = name;
Mantra = mantra;
Action = action;
Reagents = regs;
AllowTown = allowTown;
LeftHandEffect = leftHandEffect;
RightHandEffect = rightHandEffect;
Amounts = new int[regs.Length];
for (int i = 0; i < regs.Length; ++i)
Amounts[i] = 1;
}
public int Action{ get; set; }
public bool AllowTown{ get; set; }
public int[] Amounts{ get; set; }
public string Mantra{ get; set; }
public string Name{ get; set; }
public Type[] Reagents{ get; set; }
public int LeftHandEffect{ get; set; }
public int RightHandEffect{ get; set; }
}
}

View file

@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
namespace Server.Spells
{
public class SpellRegistry
{
private static Type[] m_Types = new Type[700];
private static int m_Count;
private static Dictionary<Type, int> m_IDsFromTypes = new Dictionary<Type, int>(m_Types.Length);
private static object[] m_Params = new object[2];
private static string[] m_CircleNames =
{
"First",
"Second",
"Third",
"Fourth",
"Fifth",
"Sixth",
"Seventh",
"Eighth",
"Necromancy",
"Chivalry",
"Bushido",
"Ninjitsu",
"Spellweaving"
};
public static Type[] Types
{
get
{
m_Count = -1;
return m_Types;
}
}
//What IS this used for anyways.
public static int Count
{
get
{
if (m_Count == -1)
{
m_Count = 0;
for (int i = 0; i < m_Types.Length; ++i)
if (m_Types[i] != null)
++m_Count;
}
return m_Count;
}
}
public static Dictionary<int, SpecialMove> SpecialMoves{ get; } = new Dictionary<int, SpecialMove>();
public static int GetRegistryNumber(ISpell s)
{
return GetRegistryNumber(s.GetType());
}
public static int GetRegistryNumber(SpecialMove s)
{
return GetRegistryNumber(s.GetType());
}
public static int GetRegistryNumber(Type type)
{
return m_IDsFromTypes.TryGetValue(type, out int value) ? value : -1;
}
public static void Register(int spellID, Type type)
{
if (spellID < 0 || spellID >= m_Types.Length)
return;
if (m_Types[spellID] == null)
++m_Count;
m_Types[spellID] = type;
if (!m_IDsFromTypes.ContainsKey(type))
m_IDsFromTypes.Add(type, spellID);
if (type.IsSubclassOf(typeof(SpecialMove)))
{
SpecialMove spm = null;
try
{
spm = Activator.CreateInstance(type) as SpecialMove;
}
catch
{
// ignored
}
if (spm != null)
SpecialMoves.Add(spellID, spm);
}
}
public static SpecialMove GetSpecialMove(int spellID)
{
if (spellID < 0 || spellID >= m_Types.Length)
return null;
Type t = m_Types[spellID];
if (t == null || !t.IsSubclassOf(typeof(SpecialMove)))
return null;
SpecialMoves.TryGetValue(spellID, out SpecialMove move);
return move;
}
public static Spell NewSpell(int spellID, Mobile caster, Item scroll)
{
if (spellID < 0 || spellID >= m_Types.Length)
return null;
Type t = m_Types[spellID];
if (t?.IsSubclassOf(typeof(SpecialMove)) == false)
{
m_Params[0] = caster;
m_Params[1] = scroll;
try
{
return (Spell)Activator.CreateInstance(t, m_Params);
}
catch
{
// ignored
}
}
return null;
}
public static Spell NewSpell(string name, Mobile caster, Item scroll)
{
for (int i = 0; i < m_CircleNames.Length; ++i)
{
Type t = ScriptCompiler.FindTypeByFullName($"Server.Spells.{m_CircleNames[i]}.{name}");
if (t?.IsSubclassOf(typeof(SpecialMove)) == false)
{
m_Params[0] = caster;
m_Params[1] = scroll;
try
{
return (Spell)Activator.CreateInstance(t, m_Params);
}
catch
{
// ignored
}
}
}
return null;
}
}
}

View file

@ -0,0 +1,13 @@
namespace Server.Spells
{
public enum SpellState
{
None = 0,
Casting =
1, // We are in the process of casting (that is, waiting GetCastTime() and doing animations). Spell casting may be interupted in this state.
Sequencing =
2 // Casting completed, but the full spell sequence isn't. Usually waiting for a target response. Some actions are restricted in this state (using skills for example).
}
}

View file

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
namespace Server.Spells.Bushido
{
public class Confidence : SamuraiSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Confidence", null,
-1,
9002
);
private static Dictionary<Mobile, Timer> m_Table = new Dictionary<Mobile, Timer>();
private static Dictionary<Mobile, Timer> m_RegenTable = new Dictionary<Mobile, Timer>();
public Confidence(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.25);
public override double RequiredSkill => 25.0;
public override int RequiredMana => 10;
public override void OnBeginCast()
{
base.OnBeginCast();
Caster.FixedEffect(0x37C4, 10, 7, 4, 3);
}
public override void OnCast()
{
if (CheckSequence())
{
Caster.SendLocalizedMessage(1063115); // You exude confidence.
Caster.FixedParticles(0x375A, 1, 17, 0x7DA, 0x960, 0x3, EffectLayer.Waist);
Caster.PlaySound(0x51A);
OnCastSuccessful(Caster);
BeginConfidence(Caster);
BeginRegenerating(Caster);
}
FinishSequence();
}
public static bool IsConfident(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static void BeginConfidence(Mobile m)
{
m_Table.TryGetValue(m, out Timer timer);
timer?.Stop();
m_Table[m] = timer = new InternalTimer(m);
timer.Start();
}
public static void EndConfidence(Mobile m)
{
if (m_Table.TryGetValue(m, out Timer timer))
{
timer.Stop();
m_Table.Remove(m);
}
OnEffectEnd(m, typeof(Confidence));
}
public static bool IsRegenerating(Mobile m)
{
return m_RegenTable.ContainsKey(m);
}
public static void BeginRegenerating(Mobile m)
{
m_RegenTable.TryGetValue(m, out Timer timer);
timer?.Stop();
m_RegenTable[m] = timer = new RegenTimer(m);
timer.Start();
}
public static void StopRegenerating(Mobile m)
{
if (m_RegenTable.TryGetValue(m, out Timer timer))
{
timer.Stop();
m_RegenTable.Remove(m);
}
}
private class InternalTimer : Timer
{
private Mobile m_Mobile;
public InternalTimer(Mobile m) : base(TimeSpan.FromSeconds(15.0))
{
m_Mobile = m;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
EndConfidence(m_Mobile);
m_Mobile.SendLocalizedMessage(1063116); // Your confidence wanes.
}
}
private class RegenTimer : Timer
{
private int m_Hits;
private Mobile m_Mobile;
private int m_Ticks;
public RegenTimer(Mobile m) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0))
{
m_Mobile = m;
m_Hits = 15 + m.Skills.Bushido.Fixed * m.Skills.Bushido.Fixed / 57600;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
++m_Ticks;
if (m_Ticks >= 5)
{
m_Mobile.Hits += m_Hits - m_Hits * 4 / 5;
StopRegenerating(m_Mobile);
}
m_Mobile.Hits += m_Hits / 5;
}
}
}
}

View file

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using Server.Items;
namespace Server.Spells.Bushido
{
public class CounterAttack : SamuraiSpell
{
private static SpellInfo m_Info = new SpellInfo(
"CounterAttack", null,
-1,
9002
);
private static Dictionary<Mobile, Timer> m_Table = new Dictionary<Mobile, Timer>();
public CounterAttack(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.25);
public override double RequiredSkill => 40.0;
public override int RequiredMana => 5;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.FindItemOnLayer(Layer.TwoHanded) is BaseShield)
return true;
if (Caster.FindItemOnLayer(Layer.OneHanded) is BaseWeapon)
return true;
if (Caster.FindItemOnLayer(Layer.TwoHanded) is BaseWeapon)
return true;
Caster.SendLocalizedMessage(1062944); // You must have a weapon or a shield equipped to use this ability!
return false;
}
public override void OnBeginCast()
{
base.OnBeginCast();
Caster.FixedEffect(0x37C4, 10, 7, 4, 3);
}
public override void OnCast()
{
if (CheckSequence())
{
Caster.SendLocalizedMessage(1063118); // You prepare to respond immediately to the next blocked blow.
OnCastSuccessful(Caster);
StartCountering(Caster);
}
FinishSequence();
}
public static bool IsCountering(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static void StartCountering(Mobile m)
{
m_Table.TryGetValue(m, out Timer timer);
timer?.Stop();
m_Table[m] = timer = new InternalTimer(m);
timer.Start();
}
public static void StopCountering(Mobile m)
{
if (m_Table.TryGetValue(m, out Timer timer))
{
timer.Stop();
m_Table.Remove(m);
}
OnEffectEnd(m, typeof(CounterAttack));
}
private class InternalTimer : Timer
{
private Mobile m_Mobile;
public InternalTimer(Mobile m) : base(TimeSpan.FromSeconds(30.0))
{
m_Mobile = m;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
StopCountering(m_Mobile);
m_Mobile.SendLocalizedMessage(1063119); // You return to your normal stance.
}
}
}
}

View file

@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using Server.Items;
namespace Server.Spells.Bushido
{
public class Evasion : SamuraiSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Evasion", null,
-1,
9002
);
private static Dictionary<Mobile, Timer> m_Table = new Dictionary<Mobile, Timer>();
public Evasion(Mobile caster, Item scroll)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.25);
public override double RequiredSkill => 60.0;
public override int RequiredMana => 10;
public override bool CheckCast()
{
return VerifyCast(Caster, true) && base.CheckCast();
}
public static bool VerifyCast(Mobile Caster, bool messages)
{
if (Caster == null) // Sanity
return false;
if (!(Caster.FindItemOnLayer(Layer.OneHanded) is BaseWeapon weap))
weap = Caster.FindItemOnLayer(Layer.TwoHanded) as BaseWeapon;
if (weap != null)
{
if (Core.ML && Caster.Skills[weap.Skill].Base < 50)
{
if (messages)
Caster.SendLocalizedMessage(
1076206); // Your skill with your equipped weapon must be 50 or higher to use Evasion.
return false;
}
}
else if (!(Caster.FindItemOnLayer(Layer.TwoHanded) is BaseShield))
{
if (messages)
Caster.SendLocalizedMessage(1062944); // You must have a weapon or a shield equipped to use this ability!
return false;
}
if (!Caster.CanBeginAction<Evasion>())
{
if (messages) Caster.SendLocalizedMessage(501789); // You must wait before trying again.
return false;
}
return true;
}
public static bool CheckSpellEvasion(Mobile defender)
{
if (!(defender.FindItemOnLayer(Layer.OneHanded) is BaseWeapon weap))
weap = defender.FindItemOnLayer(Layer.TwoHanded) as BaseWeapon;
if (Core.ML)
{
if (defender.Spell != null && defender.Spell.IsCasting) return false;
if (weap != null)
{
if (defender.Skills[weap.Skill].Base < 50) return false;
}
else if (!(defender.FindItemOnLayer(Layer.TwoHanded) is BaseShield))
{
return false;
}
}
if (IsEvading(defender) && BaseWeapon.CheckParry(defender))
{
defender.Emote("*evades*"); // Yes. Eew. Blame OSI.
defender.FixedEffect(0x37B9, 10, 16);
return true;
}
return false;
}
public override void OnBeginCast()
{
base.OnBeginCast();
Caster.FixedEffect(0x37C4, 10, 7, 4, 3);
}
public override void OnCast()
{
if (CheckSequence())
{
Caster.SendLocalizedMessage(1063120); // You feel that you might be able to deflect any attack!
Caster.FixedParticles(0x376A, 1, 20, 0x7F5, 0x960, 3, EffectLayer.Waist);
Caster.PlaySound(0x51B);
OnCastSuccessful(Caster);
BeginEvasion(Caster);
Caster.BeginAction<Evasion>();
Timer.DelayCall(TimeSpan.FromSeconds(20.0), delegate { Caster.EndAction<Evasion>(); });
}
FinishSequence();
}
public static bool IsEvading(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static TimeSpan GetEvadeDuration(Mobile m)
{
/* Evasion duration now scales with Bushido skill
*
* If the player has higher than GM Bushido, and GM Tactics and Anatomy, they get a 1 second bonus
* Evasion duration range:
* o 3-6 seconds w/o tactics/anatomy
* o 6-7 seconds w/ GM+ Bushido and GM tactics/anatomy
*/
if (!Core.ML)
return TimeSpan.FromSeconds(8.0);
double seconds = 3;
if (m.Skills.Bushido.Value > 60)
seconds += (m.Skills.Bushido.Value - 60) / 20;
if (m.Skills.Anatomy.Value >= 100.0 && m.Skills.Tactics.Value >= 100.0 && m.Skills.Bushido.Value > 100.0
) //Bushido being HIGHER than 100 for bonus is intended
seconds++;
return TimeSpan.FromSeconds((int)seconds);
}
public static double GetParryScalar(Mobile m)
{
/* Evasion modifier to parry now scales with Bushido skill
*
* If the player has higher than GM Bushido, and at least GM Tactics and Anatomy, they get a bonus to their evasion modifier (10% bonus to the evasion modifier to parry NOT 10% to the final parry chance)
*
* Bonus modifier to parry range: (these are the ranges for the evasion modifier)
* o 16-40% bonus w/o tactics/anatomy
* o 42-50% bonus w/ GM+ bushido and GM tactics/anatomy
*/
if (!Core.ML)
return 1.5;
double bonus = 0;
if (m.Skills.Bushido.Value >= 60)
bonus += (m.Skills.Bushido.Value - 60) * .004 + 0.16;
if (m.Skills.Anatomy.Value >= 100 && m.Skills.Tactics.Value >= 100 && m.Skills.Bushido.Value > 100
) //Bushido being HIGHER than 100 for bonus is intended
bonus += 0.10;
return 1.0 + bonus;
}
public static void BeginEvasion(Mobile m)
{
m_Table.TryGetValue(m, out Timer timer);
timer?.Stop();
m_Table[m] = timer = new InternalTimer(m, GetEvadeDuration(m));
timer.Start();
}
public static void EndEvasion(Mobile m)
{
if (m_Table.TryGetValue(m, out Timer timer))
{
timer.Stop();
m_Table.Remove(m);
}
OnEffectEnd(m, typeof(Evasion));
}
private class InternalTimer : Timer
{
private Mobile m_Mobile;
public InternalTimer(Mobile m, TimeSpan delay)
: base(delay)
{
m_Mobile = m;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
EndEvasion(m_Mobile);
m_Mobile.SendLocalizedMessage(1063121); // You no longer feel that you could deflect any attack.
}
}
}
}

View file

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
namespace Server.Spells.Bushido
{
public class HonorableExecution : SamuraiMove
{
private static Dictionary<Mobile, HonorableExecutionInfo> m_Table = new Dictionary<Mobile, HonorableExecutionInfo>();
public override int BaseMana => 0;
public override double RequiredSkill => 25.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063122); // You better kill your enemy with your next hit or you'll be rather sorry...
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
double bushido = attacker.Skills.Bushido.Value;
// TODO: 20 -> Perfection
return 1.0 + bushido * 20 / 10000;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
ClearCurrentMove(attacker);
if (m_Table.TryGetValue(attacker, out HonorableExecutionInfo info))
{
info.Clear();
info.m_Timer?.Stop();
}
if (!defender.Alive)
{
attacker.FixedParticles(0x373A, 1, 17, 0x7E2, EffectLayer.Waist);
double bushido = attacker.Skills.Bushido.Value;
attacker.Hits += 20 + (int)(bushido * bushido / 480.0);
int swingBonus = Math.Max(1, (int)(bushido * bushido / 720.0));
info = new HonorableExecutionInfo(attacker, swingBonus);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(20.0), RemovePenalty, info.m_Mobile);
m_Table[attacker] = info;
}
else
{
List<object> mods = new List<object>
{
new ResistanceMod(ResistanceType.Physical, -40),
new ResistanceMod(ResistanceType.Fire, -40),
new ResistanceMod(ResistanceType.Cold, -40),
new ResistanceMod(ResistanceType.Poison, -40),
new ResistanceMod(ResistanceType.Energy, -40)
};
double resSpells = attacker.Skills.MagicResist.Value;
if (resSpells > 0.0)
mods.Add(new DefaultSkillMod(SkillName.MagicResist, true, -resSpells));
info = new HonorableExecutionInfo(attacker, mods);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(7.0), RemovePenalty, info.m_Mobile);
m_Table[attacker] = info;
}
CheckGain(attacker);
}
public static int GetSwingBonus(Mobile target)
{
return m_Table.TryGetValue(target, out HonorableExecutionInfo info) ? info.m_SwingBonus : 0;
}
public static bool IsUnderPenalty(Mobile target)
{
return m_Table.TryGetValue(target, out HonorableExecutionInfo info) && info.m_Penalty;
}
public static void RemovePenalty(Mobile target)
{
if (!m_Table.TryGetValue(target, out HonorableExecutionInfo info) || !info.m_Penalty)
return;
info.Clear();
info.m_Timer?.Stop();
m_Table.Remove(target);
}
private class HonorableExecutionInfo
{
public Mobile m_Mobile;
public List<object> m_Mods;
public bool m_Penalty;
public int m_SwingBonus;
public Timer m_Timer;
public HonorableExecutionInfo(Mobile from, List<object> mods) : this(from, 0, mods, mods != null)
{
}
public HonorableExecutionInfo(Mobile from, int swingBonus, List<object> mods = null, bool penalty = false)
{
m_Mobile = from;
m_SwingBonus = swingBonus;
m_Mods = mods;
m_Penalty = penalty;
Apply();
}
public void Apply()
{
if (m_Mods == null)
return;
for (int i = 0; i < m_Mods.Count; ++i)
{
object mod = m_Mods[i];
if (mod is ResistanceMod resistanceMod)
m_Mobile.AddResistanceMod(resistanceMod);
else if (mod is SkillMod skillMod)
m_Mobile.AddSkillMod(skillMod);
}
}
public void Clear()
{
if (m_Mods == null)
return;
for (int i = 0; i < m_Mods.Count; ++i)
{
object mod = m_Mods[i];
if (mod is ResistanceMod resistanceMod)
m_Mobile.RemoveResistanceMod(resistanceMod);
else if (mod is SkillMod skillMod)
m_Mobile.RemoveSkillMod(skillMod);
}
}
}
}
}

View file

@ -0,0 +1,69 @@
using Server.Mobiles;
namespace Server.Spells.Bushido
{
public class LightningStrike : SamuraiMove
{
public override int BaseMana => 5;
public override double RequiredSkill => 50.0;
public override TextDefinition AbilityMessage => new TextDefinition(1063167); // You prepare to strike quickly.
public override bool DelayedContext => true;
public override bool ValidatesDuringHit => false;
public override int GetAccuracyBonus(Mobile attacker)
{
return 50;
}
public override bool Validate(Mobile from)
{
bool isValid = base.Validate(from);
if (isValid)
{
PlayerMobile ThePlayer = from as PlayerMobile;
ThePlayer.ExecutesLightningStrike = BaseMana;
}
return isValid;
}
public override bool IgnoreArmor(Mobile attacker)
{
double bushido = attacker.Skills.Bushido.Value;
double criticalChance = bushido * bushido / 72000.0;
return criticalChance >= Utility.RandomDouble();
}
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
/* no mana drain before actual hit */
bool enoughMana = CheckMana(attacker, false);
return Validate(attacker);
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
ClearCurrentMove(attacker);
if (CheckMana(attacker, true))
{
attacker.SendLocalizedMessage(1063168); // You attack with lightning precision!
defender.SendLocalizedMessage(1063169); // Your opponent's quick strike causes extra damage!
defender.FixedParticles(0x3818, 1, 11, 0x13A8, 0, 0, EffectLayer.Waist);
defender.PlaySound(0x51D);
CheckGain(attacker);
SetContext(attacker);
}
}
public override void OnClearMove(Mobile attacker)
{
PlayerMobile
ThePlayer =
attacker as PlayerMobile; // this can be deletet if the PlayerMobile parts are moved to Server.Mobile
ThePlayer.ExecutesLightningStrike = 0;
}
}
}

View file

@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
using Server.Items;
namespace Server.Spells.Bushido
{
public class MomentumStrike : SamuraiMove
{
public override int BaseMana => 10;
public override double RequiredSkill => 70.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1070757); // You prepare to strike two enemies with one blow.
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, false))
return;
ClearCurrentMove(attacker);
BaseWeapon weapon = attacker.Weapon as BaseWeapon;
List<Mobile> targets = attacker.GetMobilesInRange(weapon.MaxRange)
.Where(m => m != defender).Where(m => m.Combatant == attacker).ToList();
if (targets.Count <= 0)
{
attacker.SendLocalizedMessage(1063123); // There are no valid targets to attack!
return;
}
if (!CheckMana(attacker, true))
return;
Mobile target = targets[Utility.Random(targets.Count)];
double damageBonus = attacker.Skills.Bushido.Value / 100.0;
if (!defender.Alive)
damageBonus *= 1.5;
attacker.SendLocalizedMessage(1063171); // You transfer the momentum of your weapon into another enemy!
target.SendLocalizedMessage(1063172); // You were hit by the momentum of a Samurai's weapon!
target.FixedParticles(0x37B9, 1, 4, 0x251D, 0, 0, EffectLayer.Waist);
attacker.PlaySound(0x510);
weapon.OnSwing(attacker, target, damageBonus);
CheckGain(attacker);
}
public override void CheckGain(Mobile m)
{
m.CheckSkill(MoveSkill, RequiredSkill, 120.0);
}
}
}

View file

@ -0,0 +1,12 @@
namespace Server.Spells
{
public class SamuraiMove : SpecialMove
{
public override SkillName MoveSkill => SkillName.Bushido;
public override void CheckGain(Mobile m)
{
m.CheckSkill(MoveSkill, RequiredSkill - 12.5, RequiredSkill + 37.5); //Per five on friday 02/16/07
}
}
}

View file

@ -0,0 +1,126 @@
using System;
using Server.Mobiles;
using Server.Network;
namespace Server.Spells.Bushido
{
public abstract class SamuraiSpell : Spell
{
public SamuraiSpell(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.Bushido;
public override SkillName DamageSkill => SkillName.Bushido;
public override bool ClearHandsOnCast => false;
public override bool BlocksMovement => false;
public override bool ShowHandMovement => false;
//public override int CastDelayBase => 1;
public override double CastDelayFastScalar => 0;
public override int CastRecoveryBase => 7;
public static bool CheckExpansion(Mobile from)
{
return (from as PlayerMobile)?.NetState?.SupportsExpansion(Expansion.SE) == true;
}
public override bool CheckCast()
{
int mana = ScaleMana(RequiredMana);
if (!base.CheckCast())
return false;
if (!CheckExpansion(Caster))
{
Caster.SendLocalizedMessage(1063456); // You must upgrade to Samurai Empire in order to use that ability.
return false;
}
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
string args = $"{RequiredSkill.ToString("F1")}\t{CastSkill.ToString()}\t ";
Caster.SendLocalizedMessage(1063013,
args); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
return true;
}
public override bool CheckFizzle()
{
int mana = ScaleMana(RequiredMana);
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
Caster.SendLocalizedMessage(1070768,
RequiredSkill.ToString("F1")); // You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
return false;
}
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 (!base.CheckFizzle())
return false;
Caster.Mana -= mana;
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 0;
}
public virtual void OnCastSuccessful(Mobile caster)
{
if (Evasion.IsEvading(caster))
Evasion.EndEvasion(caster);
if (Confidence.IsConfident(caster))
Confidence.EndConfidence(caster);
if (CounterAttack.IsCountering(caster))
CounterAttack.StopCountering(caster);
int spellID = SpellRegistry.GetRegistryNumber(this);
if (spellID > 0)
caster.Send(new ToggleSpecialAbility(spellID + 1, true));
}
public static void OnEffectEnd(Mobile caster, Type type)
{
int spellID = SpellRegistry.GetRegistryNumber(type);
if (spellID > 0)
caster.Send(new ToggleSpecialAbility(spellID + 1, false));
}
}
}

View file

@ -0,0 +1,106 @@
using System;
using Server.Engines.ConPVP;
using Server.Targeting;
namespace Server.Spells.Chivalry
{
public class CleanseByFireSpell : PaladinSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Cleanse By Fire", "Expor Flamus",
-1,
9002
);
public CleanseByFireSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 5.0;
public override int RequiredMana => 10;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060718; // Expor Flamus
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
{
Caster.SendMessage(0x22, "You cannot cast this spell when in sudden death.");
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!m.Poisoned)
Caster.SendLocalizedMessage(1060176); // That creature is not poisoned!
else if (CheckBSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Cures the target of poisons, but causes the caster to be burned by fire damage for 13-55 hit points.
* The amount of fire damage is lessened if the caster has high Karma.
*/
Poison p = m.Poison;
if (p != null)
{
// Cleanse by fire is now difficulty based
int chanceToCure = 10000 + (int)(Caster.Skills.Chivalry.Value * 75) - (p.Level + 1) * 2000;
chanceToCure /= 100;
if (chanceToCure > Utility.Random(100))
{
if (m.CurePoison(Caster))
{
if (Caster != m)
Caster.SendLocalizedMessage(1010058); // You have cured the target of all poisons!
m.SendLocalizedMessage(1010059); // You have been cured of all poisons.
}
}
else
{
m.SendLocalizedMessage(1010060); // You have failed to cure your target!
}
}
m.PlaySound(0x1E0);
m.FixedParticles(0x373A, 1, 15, 5012, 3, 2, EffectLayer.Waist);
IEntity from = new Entity(Serial.Zero, new Point3D(m.X, m.Y, m.Z - 5), m.Map);
IEntity to = new Entity(Serial.Zero, new Point3D(m.X, m.Y, m.Z + 45), m.Map);
Effects.SendMovingParticles(from, to, 0x374B, 1, 0, false, false, 63, 2, 9501, 1, 0, EffectLayer.Head,
0x100);
Caster.PlaySound(0x208);
Caster.FixedParticles(0x3709, 1, 30, 9934, 0, 7, EffectLayer.Waist);
int damage = 50 - ComputePowerValue(4);
// TODO: Should caps be applied?
if (damage < 13)
damage = 13;
else if (damage > 55)
damage = 55;
AOS.Damage(Caster, Caster, damage, 0, 100, 0, 0, 0, true);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,87 @@
using System;
using Server.Engines.ConPVP;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Spells.Chivalry
{
public class CloseWoundsSpell : PaladinSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Close Wounds", "Obsu Vulni",
-1,
9002
);
public CloseWoundsSpell(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 => 10;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060719; // Obsu Vulni
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
{
Caster.SendMessage(0x22, "You cannot cast this spell when in sudden death.");
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.InRange(m, 2))
Caster.SendLocalizedMessage(1060178); // You are too far away to perform that action!
else if (m is BaseCreature creature && creature.IsAnimatedDead)
Caster.SendLocalizedMessage(1061654); // You cannot heal that which is not alive.
else if (m.IsDeadBondedPet)
Caster.SendLocalizedMessage(1060177); // You cannot heal a creature that is already dead!
else if (m.Hits >= m.HitsMax)
Caster.SendLocalizedMessage(500955); // That being is not damaged!
else if (m.Poisoned || MortalStrike.IsWounded(m))
Caster.LocalOverheadMessage(MessageType.Regular, 0x3B2, Caster == m ? 1005000 : 1010398);
else if (CheckBSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Heals the target for 7 to 39 points of damage.
* The caster's Karma affects the amount of damage healed.
*/
// TODO: Should caps be applied?
int toHeal = Math.Min(Math.Max(ComputePowerValue(6) + Utility.RandomMinMax(0, 2), 7), 39);
if (m.Hits + toHeal > m.HitsMax)
toHeal = m.HitsMax - m.Hits;
SpellHelper.Heal(toHeal, m, Caster, false);
m.SendLocalizedMessage(1060203,
toHeal.ToString()); // You have had ~1_HEALED_AMOUNT~ hit points of damage healed.
m.PlaySound(0x202);
m.FixedParticles(0x376A, 1, 62, 9923, 3, 3, EffectLayer.Waist);
m.FixedParticles(0x3779, 1, 46, 9502, 5, 3, EffectLayer.Waist);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using Server.Items;
namespace Server.Spells.Chivalry
{
public class ConsecrateWeaponSpell : PaladinSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Consecrate Weapon", "Consecrus Arma",
-1,
9002
);
private static Dictionary<BaseWeapon, ExpireTimer> m_Table = new Dictionary<BaseWeapon, ExpireTimer>();
public ConsecrateWeaponSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.5);
public override double RequiredSkill => 15.0;
public override int RequiredMana => 10;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060720; // Consecrus Arma
public override bool BlocksMovement => false;
public override void OnCast()
{
if (!(Caster.Weapon is BaseWeapon weapon) || weapon is Fists)
{
Caster.SendLocalizedMessage(501078); // You must be holding a weapon.
}
else if (CheckSequence())
{
/* Temporarily enchants the weapon the caster is currently wielding.
* The type of damage the weapon inflicts when hitting a target will
* be converted to the target's worst Resistance type.
* Duration of the effect is affected by the caster's Karma and lasts for 3 to 11 seconds.
*/
int itemID, soundID;
switch (weapon.Skill)
{
case SkillName.Macing:
itemID = 0xFB4;
soundID = 0x232;
break;
case SkillName.Archery:
itemID = 0x13B1;
soundID = 0x145;
break;
default:
itemID = 0xF5F;
soundID = 0x56;
break;
}
Caster.PlaySound(0x20C);
Caster.PlaySound(soundID);
Caster.FixedParticles(0x3779, 1, 30, 9964, 3, 3, EffectLayer.Waist);
IEntity from = new Entity(Serial.Zero, new Point3D(Caster.X, Caster.Y, Caster.Z), Caster.Map);
IEntity to = new Entity(Serial.Zero, new Point3D(Caster.X, Caster.Y, Caster.Z + 50), Caster.Map);
Effects.SendMovingParticles(from, to, itemID, 1, 0, false, false, 33, 3, 9501, 1, 0, EffectLayer.Head,
0x100);
double seconds = ComputePowerValue(20);
// TODO: Should caps be applied?
if (seconds < 3.0)
seconds = 3.0;
else if (seconds > 11.0)
seconds = 11.0;
TimeSpan duration = TimeSpan.FromSeconds(seconds);
m_Table.TryGetValue(weapon, out ExpireTimer timer);
timer?.Stop();
weapon.Consecrated = true;
m_Table[weapon] = timer = new ExpireTimer(weapon, duration);
timer.Start();
}
FinishSequence();
}
private class ExpireTimer : Timer
{
private BaseWeapon m_Weapon;
public ExpireTimer(BaseWeapon weapon, TimeSpan delay) : base(delay)
{
m_Weapon = weapon;
Priority = TimerPriority.FiftyMS;
}
protected override void OnTick()
{
m_Weapon.Consecrated = false;
Effects.PlaySound(m_Weapon.GetWorldLocation(), m_Weapon.Map, 0x1F8);
m_Table.Remove(m_Weapon);
}
}
}
}

View file

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Items;
using Server.Mobiles;
using Server.Spells.Necromancy;
namespace Server.Spells.Chivalry
{
public class DispelEvilSpell : PaladinSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Dispel Evil", "Dispiro Malas",
-1,
9002
);
public DispelEvilSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.25);
public override double RequiredSkill => 35.0;
public override int RequiredMana => 10;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060721; // Dispiro Malas
public override bool BlocksMovement => false;
public override bool DelayedDamage => false;
public override void SendCastEffect()
{
Caster.FixedEffect(0x37C4, 10, 7, 4, 3); // At player
}
public override void OnCast()
{
if (CheckSequence())
{
Caster.PlaySound(0xF5);
Caster.PlaySound(0x299);
Caster.FixedParticles(0x37C4, 1, 25, 9922, 14, 3, EffectLayer.Head);
int dispelSkill = ComputePowerValue(2);
double chiv = Caster.Skills.Chivalry.Value;
IEnumerable<Mobile> targets = Caster.GetMobilesInRange(8)
.Where(m => Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false));
foreach (Mobile m in targets)
{
if (m is BaseCreature bc)
{
if (bc.Summoned && !bc.IsAnimatedDead)
{
double dispelChance = (50.0 + 100 * (chiv - bc.DispelDifficulty) / (bc.DispelFocus * 2)) / 100;
dispelChance *= dispelSkill / 100.0;
if (dispelChance > Utility.RandomDouble())
{
Effects.SendLocationParticles(
EffectItem.Create(m.Location, m.Map, EffectItem.DefaultDuration), 0x3728, 8, 20, 5042);
Effects.PlaySound(m, m.Map, 0x201);
m.Delete();
continue;
}
}
bool evil = !bc.Controlled && bc.Karma < 0;
if (evil)
{
// TODO: Is this right?
double fleeChance = (100 - Math.Sqrt(m.Fame / 2)) * chiv * dispelSkill;
fleeChance /= 1000000;
if (fleeChance > Utility.RandomDouble()) bc.BeginFlee(TimeSpan.FromSeconds(30.0));
}
}
TransformContext context = TransformationSpellHelper.GetContext(m);
if (context?.Spell is NecromancerSpell) //Trees are not evil! TODO: OSI confirm?
{
// transformed ..
double drainChance = 0.5 * (Caster.Skills.Chivalry.Value / Math.Max(m.Skills.Necromancy.Value, 1));
if (drainChance > Utility.RandomDouble())
{
int drain = 5 * dispelSkill / 100;
m.Stam -= drain;
m.Mana -= drain;
}
}
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
namespace Server.Spells.Chivalry
{
public class DivineFurySpell : PaladinSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Divine Fury", "Divinum Furis",
-1,
9002
);
private static Dictionary<Mobile, Timer> m_Table = new Dictionary<Mobile, Timer>();
public DivineFurySpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 25.0;
public override int RequiredMana => 15;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060722; // Divinum Furis
public override bool BlocksMovement => false;
public override void OnCast()
{
if (CheckSequence())
{
Caster.PlaySound(0x20F);
Caster.PlaySound(Caster.Female ? 0x338 : 0x44A);
Caster.FixedParticles(0x376A, 1, 31, 9961, 1160, 0, EffectLayer.Waist);
Caster.FixedParticles(0x37C4, 1, 31, 9502, 43, 2, EffectLayer.Waist);
Caster.Stam = Caster.StamMax;
m_Table.TryGetValue(Caster, out Timer timer);
timer?.Stop();
int delay = ComputePowerValue(10);
// TODO: Should caps be applied?
if (delay < 7)
delay = 7;
else if (delay > 24)
delay = 24;
m_Table[Caster] = Timer.DelayCall(TimeSpan.FromSeconds(delay), Expire_Callback, Caster);
Caster.Delta(MobileDelta.WeaponDamage);
BuffInfo.AddBuff(Caster,
new BuffInfo(BuffIcon.DivineFury, 1060589, 1075634, TimeSpan.FromSeconds(delay), Caster));
}
FinishSequence();
}
public static bool UnderEffect(Mobile m)
{
return m_Table.ContainsKey(m);
}
private static void Expire_Callback(Mobile m)
{
m_Table.Remove(m);
m.Delta(MobileDelta.WeaponDamage);
m.PlaySound(0xF8);
}
}
}

View file

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using Server.Mobiles;
namespace Server.Spells.Chivalry
{
public class EnemyOfOneSpell : PaladinSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Enemy of One", "Forul Solum",
-1,
9002
);
private static Dictionary<Mobile, Timer> m_Table = new Dictionary<Mobile, Timer>();
public EnemyOfOneSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.5);
public override double RequiredSkill => 45.0;
public override int RequiredMana => 20;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060723; // Forul Solum
public override bool BlocksMovement => false;
public override void OnCast()
{
if (CheckSequence())
{
Caster.PlaySound(0x0F5);
Caster.PlaySound(0x1ED);
Caster.FixedParticles(0x375A, 1, 30, 9966, 33, 2, EffectLayer.Head);
Caster.FixedParticles(0x37B9, 1, 30, 9502, 43, 3, EffectLayer.Head);
m_Table.TryGetValue(Caster, out Timer timer);
timer?.Stop();
double delay = (double)ComputePowerValue(1) / 60;
// TODO: Should caps be applied?
if (delay < 1.5)
delay = 1.5;
else if (delay > 3.5)
delay = 3.5;
m_Table[Caster] = Timer.DelayCall(TimeSpan.FromMinutes(delay), Expire_Callback, Caster);
if (Caster is PlayerMobile mobile)
{
mobile.EnemyOfOneType = null;
mobile.WaitingForEnemy = true;
BuffInfo.AddBuff(mobile,
new BuffInfo(BuffIcon.EnemyOfOne, 1075653, 1044111, TimeSpan.FromMinutes(delay), mobile));
}
}
FinishSequence();
}
private static void Expire_Callback(Mobile m)
{
m_Table.Remove(m);
m.PlaySound(0x1F8);
if (m is PlayerMobile mobile)
{
mobile.EnemyOfOneType = null;
mobile.WaitingForEnemy = false;
}
}
}
}

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Items;
namespace Server.Spells.Chivalry
{
public class HolyLightSpell : PaladinSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Holy Light", "Augus Luminos",
-1,
9002
);
public HolyLightSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.75);
public override double RequiredSkill => 55.0;
public override int RequiredMana => 10;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060724; // Augus Luminos
public override bool BlocksMovement => false;
public override bool DelayedDamage => false;
public override void OnCast()
{
if (CheckSequence())
{
Caster.PlaySound(0x212);
Caster.PlaySound(0x206);
Effects.SendLocationParticles(EffectItem.Create(Caster.Location, Caster.Map, EffectItem.DefaultDuration),
0x376A, 1, 29, 0x47D, 2, 9962, 0);
Effects.SendLocationParticles(
EffectItem.Create(new Point3D(Caster.X, Caster.Y, Caster.Z - 7), Caster.Map, EffectItem.DefaultDuration),
0x37C4, 1, 29, 0x47D, 2, 9502, 0);
IEnumerable<Mobile> targets = Caster.GetMobilesInRange(3)
.Where(m => Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) &&
(!Core.AOS || Caster.InLOS(m)));
foreach (Mobile m in targets)
{
int damage = ComputePowerValue(10) + Utility.RandomMinMax(0, 2);
// TODO: Should caps be applied?
if (damage < 8)
damage = 8;
else if (damage > 24)
damage = 24;
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 100);
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Spells.Necromancy;
namespace Server.Spells.Chivalry
{
public class NobleSacrificeSpell : PaladinSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Noble Sacrifice", "Dium Prostra",
-1,
9002
);
public NobleSacrificeSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 65.0;
public override int RequiredMana => 20;
public override int RequiredTithing => 30;
public override int MantraNumber => 1060725; // Dium Prostra
public override bool BlocksMovement => false;
public override void OnCast()
{
if (CheckSequence())
{
List<Mobile> targets = new List<Mobile>();
foreach (Mobile m in Caster.GetMobilesInRange(3)) // TODO: Validate range
{
if (m is BaseCreature creature && creature.IsAnimatedDead)
continue;
if (Caster != m && m.InLOS(Caster) && Caster.CanBeBeneficial(m, false, true) && !(m is Golem))
targets.Add(m);
}
Caster.PlaySound(0x244);
Caster.FixedParticles(0x3709, 1, 30, 9965, 5, 7, EffectLayer.Waist);
Caster.FixedParticles(0x376A, 1, 30, 9502, 5, 3, EffectLayer.Waist);
/* Attempts to Resurrect, Cure and Heal all targets in a radius around the caster.
* If any target is successfully assisted, the Paladin's current
* Hit Points, Mana and Stamina are set to 1.
* Amount of damage healed is affected by the Caster's Karma, from 8 to 24 hit points.
*/
bool sacrifice = false;
// TODO: Is there really a resurrection chance?
double resChance = 0.1 + 0.9 * Caster.Karma / 10000.0d;
for (int i = 0; i < targets.Count; ++i)
{
Mobile m = targets[i];
if (!m.Alive)
{
if (m.Region?.IsPartOf("Khaldun") == true)
{
Caster.SendLocalizedMessage(
1010395); // The veil of death in this area is too strong and resists thy efforts to restore life.
}
else if (resChance > Utility.RandomDouble())
{
m.FixedParticles(0x375A, 1, 15, 5005, 5, 3, EffectLayer.Head);
m.CloseGump<ResurrectGump>();
m.SendGump(new ResurrectGump(m, Caster));
sacrifice = true;
}
}
else
{
bool sendEffect = false;
if (m.Poisoned && m.CurePoison(Caster))
{
Caster.DoBeneficial(m);
if (Caster != m)
Caster.SendLocalizedMessage(1010058); // You have cured the target of all poisons!
m.SendLocalizedMessage(1010059); // You have been cured of all poisons.
sendEffect = true;
sacrifice = true;
}
if (m.Hits < m.HitsMax)
{
int toHeal = ComputePowerValue(10) + Utility.RandomMinMax(0, 2);
// TODO: Should caps be applied?
if (toHeal < 8)
toHeal = 8;
else if (toHeal > 24)
toHeal = 24;
Caster.DoBeneficial(m);
m.Heal(toHeal, Caster);
sendEffect = true;
}
StatMod mod;
mod = m.GetStatMod("[Magic] Str Offset");
if (mod?.Offset < 0)
{
m.RemoveStatMod("[Magic] Str Offset");
sendEffect = true;
}
mod = m.GetStatMod("[Magic] Dex Offset");
if (mod?.Offset < 0)
{
m.RemoveStatMod("[Magic] Dex Offset");
sendEffect = true;
}
mod = m.GetStatMod("[Magic] Int Offset");
if (mod?.Offset < 0)
{
m.RemoveStatMod("[Magic] Int Offset");
sendEffect = true;
}
if (m.Paralyzed)
{
m.Paralyzed = false;
sendEffect = true;
}
if (EvilOmenSpell.TryEndEffect(m))
sendEffect = true;
if (StrangleSpell.RemoveCurse(m))
sendEffect = true;
if (CorpseSkinSpell.RemoveCurse(m))
sendEffect = true;
// TODO: Should this remove blood oath? Pain spike?
if (sendEffect)
{
m.FixedParticles(0x375A, 1, 15, 5005, 5, 3, EffectLayer.Head);
sacrifice = true;
}
}
}
if (sacrifice)
{
Caster.PlaySound(Caster.Body.IsFemale ? 0x150 : 0x423);
Caster.Hits = 1;
Caster.Stam = 1;
Caster.Mana = 1;
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,147 @@
using System;
using Server.Network;
namespace Server.Spells.Chivalry
{
public abstract class PaladinSpell : Spell
{
public PaladinSpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info)
{
}
public abstract double RequiredSkill{ get; }
public abstract int RequiredMana{ get; }
public abstract int RequiredTithing{ get; }
public abstract int MantraNumber{ get; }
public override SkillName CastSkill => SkillName.Chivalry;
public override SkillName DamageSkill => SkillName.Chivalry;
public override bool ClearHandsOnCast => false;
//public override int CastDelayBase => 1;
public override int CastRecoveryBase => 7;
public override bool CheckCast()
{
int mana = ScaleMana(RequiredMana);
if (!base.CheckCast())
return false;
if (Caster.TithingPoints < RequiredTithing)
{
Caster.SendLocalizedMessage(1060173,
RequiredTithing
.ToString()); // You must have at least ~1_TITHE_REQUIREMENT~ Tithing Points to use this ability,
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
return true;
}
public override bool CheckFizzle()
{
int requiredTithing = RequiredTithing;
if (AosAttributes.GetValue(Caster, AosAttribute.LowerRegCost) > Utility.Random(100))
requiredTithing = 0;
int mana = ScaleMana(RequiredMana);
if (Caster.TithingPoints < requiredTithing)
{
Caster.SendLocalizedMessage(1060173,
RequiredTithing
.ToString()); // You must have at least ~1_TITHE_REQUIREMENT~ Tithing Points to use this ability,
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
Caster.TithingPoints -= requiredTithing;
if (!base.CheckFizzle())
return false;
Caster.Mana -= mana;
return true;
}
public override void SayMantra()
{
Caster.PublicOverheadMessage(MessageType.Regular, 0x3B2, MantraNumber, "", false);
}
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();
}
public virtual void SendCastEffect()
{
Caster.FixedEffect(0x37C4, 10, (int)(GetCastDelay().TotalSeconds * 28), 4, 3);
}
public override void GetCastSkills(out double min, out double max)
{
min = RequiredSkill;
max = RequiredSkill + 50.0;
}
public override int GetMana()
{
return 0;
}
public int ComputePowerValue(int div)
{
return ComputePowerValue(Caster, div);
}
public static int ComputePowerValue(Mobile from, int div)
{
if (from == null)
return 0;
int v = (int)Math.Sqrt(from.Karma + 20000 + from.Skills.Chivalry.Fixed * 10);
return v / div;
}
}
}

View file

@ -0,0 +1,123 @@
using System;
using Server.Engines.ConPVP;
using Server.Items;
using Server.Spells.Fourth;
using Server.Spells.Necromancy;
using Server.Targeting;
namespace Server.Spells.Chivalry
{
public class RemoveCurseSpell : PaladinSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Remove Curse", "Extermo Vomica",
-1,
9002
);
public RemoveCurseSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 5.0;
public override int RequiredMana => 20;
public override int RequiredTithing => 10;
public override int MantraNumber => 1060726; // Extermo Vomica
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
{
Caster.SendMessage(0x22, "You cannot cast this spell when in sudden death.");
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (CheckBSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Attempts to remove all Curse effects from Target.
* Curses include Mage spells such as Clumsy, Weaken, Feeblemind and Paralyze
* as well as all Necromancer curses.
* Chance of removing curse is affected by Caster's Karma.
*/
int chance;
if (Caster.Karma < -5000)
chance = 0;
else if (Caster.Karma < 0)
chance = (int)Math.Sqrt(20000 + Caster.Karma) - 122;
else if (Caster.Karma < 5625)
chance = (int)Math.Sqrt(Caster.Karma) + 25;
else
chance = 100;
if (chance > Utility.Random(100))
{
m.PlaySound(0xF6);
m.PlaySound(0x1F7);
m.FixedParticles(0x3709, 1, 30, 9963, 13, 3, EffectLayer.Head);
IEntity from = new Entity(Serial.Zero, new Point3D(m.X, m.Y, m.Z - 10), Caster.Map);
IEntity to = new Entity(Serial.Zero, new Point3D(m.X, m.Y, m.Z + 50), Caster.Map);
Effects.SendMovingParticles(from, to, 0x2255, 1, 0, false, false, 13, 3, 9501, 1, 0, EffectLayer.Head,
0x100);
StatMod mod = m.GetStatMod("[Magic] Str Offset");
if (mod?.Offset < 0)
m.RemoveStatMod("[Magic] Str Offset");
mod = m.GetStatMod("[Magic] Dex Offset");
if (mod?.Offset < 0)
m.RemoveStatMod("[Magic] Dex Offset");
mod = m.GetStatMod("[Magic] Int Offset");
if (mod?.Offset < 0)
m.RemoveStatMod("[Magic] Int Offset");
m.Paralyzed = false;
EvilOmenSpell.TryEndEffect(m);
StrangleSpell.RemoveCurse(m);
CorpseSkinSpell.RemoveCurse(m);
CurseSpell.RemoveEffect(m);
MortalStrike.EndWound(m);
if (Core.ML) BloodOathSpell.RemoveCurse(m);
MindRotSpell.ClearMindRotScalar(m);
BuffInfo.RemoveBuff(m, BuffIcon.Clumsy);
BuffInfo.RemoveBuff(m, BuffIcon.FeebleMind);
BuffInfo.RemoveBuff(m, BuffIcon.Weaken);
BuffInfo.RemoveBuff(m, BuffIcon.Curse);
BuffInfo.RemoveBuff(m, BuffIcon.MassCurse);
BuffInfo.RemoveBuff(m, BuffIcon.MortalStrike);
BuffInfo.RemoveBuff(m, BuffIcon.Mindrot);
// TODO: Should this remove blood oath? Pain spike?
}
else
{
m.PlaySound(0x1DF);
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,142 @@
using System;
using Server.Factions;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
namespace Server.Spells.Chivalry
{
public class SacredJourneySpell : PaladinSpell, IRecallSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Sacred Journey", "Sanctum Viatas",
-1,
9002
);
private Runebook m_Book;
private RunebookEntry m_Entry;
public SacredJourneySpell(Mobile caster, RunebookEntry entry = null, Runebook book = null, Item scroll = null) :
base(caster, scroll, m_Info)
{
m_Entry = entry;
m_Book = book;
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 15.0;
public override int RequiredMana => 10;
public override int RequiredTithing => 15;
public override int MantraNumber => 1060727; // Sanctum Viatas
public override bool BlocksMovement => false;
public override void OnCast()
{
if (m_Entry == null)
Caster.Target = new RecallSpellTarget(this);
else
Effect(m_Entry.Location, m_Entry.Map, true);
}
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
return false;
}
if (Caster.Criminal)
{
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
return false;
}
if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1061282); // You cannot use the Sacred Journey ability to flee from combat.
return false;
}
if (WeightOverloading.IsOverloaded(Caster))
{
Caster.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
return false;
}
return SpellHelper.CheckTravel(Caster, TravelCheckType.RecallFrom);
}
public void Effect(Point3D loc, Map map, bool checkMulti)
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (map == null || !Core.AOS && Caster.Map != map)
{
Caster.SendLocalizedMessage(1005569); // You can not recall to another facet.
}
else if (!SpellHelper.CheckTravel(Caster, TravelCheckType.RecallFrom))
{
}
else if (!SpellHelper.CheckTravel(Caster, map, loc, TravelCheckType.RecallTo))
{
}
else if (map == Map.Felucca && Caster is PlayerMobile mobile && mobile.Young)
{
mobile.SendLocalizedMessage(1049543); // You decide against traveling to Felucca while you are still young.
}
else if (Caster.Kills >= 5 && map != Map.Felucca)
{
Caster.SendLocalizedMessage(1019004); // You are not allowed to travel there.
}
else if (Caster.Criminal)
{
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
}
else if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1061282); // You cannot use the Sacred Journey ability to flee from combat.
}
else if (WeightOverloading.IsOverloaded(Caster))
{
Caster.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
}
else if (!map.CanSpawnMobile(loc.X, loc.Y, loc.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (checkMulti && SpellHelper.CheckMulti(loc, map))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (m_Book != null && m_Book.CurCharges <= 0)
{
Caster.SendLocalizedMessage(502412); // There are no charges left on that item.
}
else if (CheckSequence())
{
BaseCreature.TeleportPets(Caster, loc, map, true);
if (m_Book != null)
--m_Book.CurCharges;
Effects.SendLocationParticles(EffectItem.Create(Caster.Location, Caster.Map, EffectItem.DefaultDuration), 0,
0, 0, 5033);
Caster.PlaySound(0x1FC);
Caster.MoveToWorld(loc, map);
Caster.PlaySound(0x1FC);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,53 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Eighth
{
public class AirElementalSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Air Elemental", "Kal Vas Xen Hur",
269,
9010,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
public AirElementalSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 2 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public override void OnCast()
{
if (CheckSequence())
{
TimeSpan duration = TimeSpan.FromSeconds(2 * Caster.Skills.Magery.Fixed / 5);
if (Core.AOS)
SpellHelper.Summon(new SummonedAirElemental(), Caster, 0x217, duration, false, false);
else
SpellHelper.Summon(new AirElemental(), Caster, 0x217, duration, false, false);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,53 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Eighth
{
public class EarthElementalSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Earth Elemental", "Kal Vas Xen Ylem",
269,
9020,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
public EarthElementalSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 2 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public override void OnCast()
{
if (CheckSequence())
{
TimeSpan duration = TimeSpan.FromSeconds(2 * Caster.Skills.Magery.Fixed / 5);
if (Core.AOS)
SpellHelper.Summon(new SummonedEarthElemental(), Caster, 0x217, duration, false, false);
else
SpellHelper.Summon(new EarthElemental(), Caster, 0x217, duration, false, false);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Server.Spells.Eighth
{
public class EarthquakeSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Earthquake", "In Vas Por",
233,
9012,
false,
Reagent.Bloodmoss,
Reagent.Ginseng,
Reagent.MandrakeRoot,
Reagent.SulfurousAsh
);
public EarthquakeSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool DelayedDamage => !Core.AOS;
public override void OnCast()
{
if (SpellHelper.CheckTown(Caster, Caster) && CheckSequence())
{
Caster.PlaySound(0x220);
if (Caster.Map == null)
{
FinishSequence();
return;
}
IEnumerable<Mobile> targets = Caster.GetMobilesInRange(1 + (int)(Caster.Skills.Magery.Value / 15.0))
.Where(m => Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) && (!Core.AOS || Caster.InLOS(m)));
foreach (Mobile m in targets)
{
int damage;
if (Core.AOS)
{
damage = m.Hits / 2;
if (!m.Player)
damage = Math.Max(Math.Min(damage, 100), 15);
damage += Utility.RandomMinMax(0, 15);
}
else
{
damage = m.Hits * 6 / 10;
if (!m.Player && damage < 10)
damage = 10;
else if (damage > 75)
damage = 75;
}
Caster.DoHarmful(m);
SpellHelper.Damage(TimeSpan.Zero, m, Caster, damage, 100, 0, 0, 0, 0);
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,69 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Eighth
{
public class EnergyVortexSpell : MagerySpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Energy Vortex", "Vas Corp Por",
260,
9032,
false,
Reagent.Bloodmoss,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.Nightshade
);
public EnergyVortexSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + (Core.SE ? 2 : 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);
}
public void Target(IPoint3D p)
{
Map map = Caster.Map;
SpellHelper.GetSurfaceTop(ref p);
if (map == null || !map.CanSpawnMobile(p.X, p.Y, p.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
TimeSpan duration;
if (Core.AOS)
duration = TimeSpan.FromSeconds(90.0);
else
duration = TimeSpan.FromSeconds(Utility.Random(80, 40));
BaseCreature.Summon(new EnergyVortex(), false, Caster, new Point3D(p), 0x212, duration);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,54 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Eighth
{
public class FireElementalSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Fire Elemental", "Kal Vas Xen Flam",
269,
9050,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
public FireElementalSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 4 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public override void OnCast()
{
if (CheckSequence())
{
TimeSpan duration = TimeSpan.FromSeconds(2 * Caster.Skills.Magery.Fixed / 5);
if (Core.AOS)
SpellHelper.Summon(new SummonedFireElemental(), Caster, 0x217, duration, false, false);
else
SpellHelper.Summon(new FireElemental(), Caster, 0x217, duration, false, false);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,79 @@
using Server.Engines.ConPVP;
using Server.Gumps;
using Server.Targeting;
namespace Server.Spells.Eighth
{
public class ResurrectionSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Resurrection", "An Corp",
245,
9062,
Reagent.Bloodmoss,
Reagent.Garlic,
Reagent.Ginseng
);
public ResurrectionSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
{
Caster.SendMessage(0x22, "You cannot cast this spell when in sudden death.");
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, 1);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (m == Caster)
Caster.SendLocalizedMessage(501039); // Thou can not resurrect thyself.
else if (!Caster.Alive)
Caster.SendLocalizedMessage(501040); // The resurrecter must be alive.
else if (m.Alive)
Caster.SendLocalizedMessage(501041); // Target is not dead.
else if (!Caster.InRange(m, 1))
Caster.SendLocalizedMessage(501042); // Target is not close enough.
else if (!m.Player)
Caster.SendLocalizedMessage(501043); // Target is not a being.
else if (m.Map == null || !m.Map.CanFit(m.Location, 16, false, false))
{
Caster.SendLocalizedMessage(501042); // Target can not be resurrected at that location.
m.SendLocalizedMessage(502391); // Thou can not be resurrected there!
}
else if (m.Region?.IsPartOf("Khaldun") == true)
Caster.SendLocalizedMessage(
1010395); // The veil of death in this area is too strong and resists thy efforts to restore life.
else if (CheckBSequence(m, true))
{
SpellHelper.Turn(Caster, m);
m.PlaySound(0x214);
m.FixedEffect(0x376A, 10, 16);
m.CloseGump<ResurrectGump>();
m.SendGump(new ResurrectGump(m, Caster));
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,60 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Eighth
{
public class SummonDaemonSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Summon Daemon", "Kal Vas Xen Corp",
269,
9050,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
public SummonDaemonSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + (Core.SE ? 4 : 5) > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public override void OnCast()
{
if (CheckSequence())
{
TimeSpan duration = TimeSpan.FromSeconds(2 * Caster.Skills.Magery.Fixed / 5);
if (Core.AOS) /* Why two diff daemons? TODO: solve this */
{
BaseCreature m_Daemon = new SummonedDaemon();
SpellHelper.Summon(m_Daemon, Caster, 0x216, duration, false, false);
m_Daemon.FixedParticles(0x3728, 8, 20, 5042, EffectLayer.Head);
}
else
{
SpellHelper.Summon(new Daemon(), Caster, 0x216, duration, false, false);
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,53 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Eighth
{
public class WaterElementalSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Water Elemental", "Kal Vas Xen An Flam",
269,
9070,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
public WaterElementalSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Eighth;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 3 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public override void OnCast()
{
if (CheckSequence())
{
TimeSpan duration = TimeSpan.FromSeconds(2 * Caster.Skills.Magery.Fixed / 5);
if (Core.AOS)
SpellHelper.Summon(new SummonedWaterElemental(), Caster, 0x217, duration, false, false);
else
SpellHelper.Summon(new WaterElemental(), Caster, 0x217, duration, false, false);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,76 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Fifth
{
public class BladeSpiritsSpell : MagerySpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Blade Spirits", "In Jux Hur Ylem",
266,
9040,
false,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.Nightshade
);
public BladeSpiritsSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override TimeSpan GetCastDelay()
{
if (Core.AOS)
return TimeSpan.FromTicks(base.GetCastDelay().Ticks * (Core.SE ? 3 : 5));
return base.GetCastDelay() + TimeSpan.FromSeconds(6.0);
}
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + (Core.SE ? 2 : 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);
}
public void Target(IPoint3D p)
{
Map map = Caster.Map;
SpellHelper.GetSurfaceTop(ref p);
if (map == null || !map.CanSpawnMobile(p.X, p.Y, p.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
TimeSpan duration;
if (Core.AOS)
duration = TimeSpan.FromSeconds(120);
else
duration = TimeSpan.FromSeconds(Utility.Random(80, 40));
BaseCreature.Summon(new BladeSpirits(), false, Caster, new Point3D(p), 0x212, duration);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,54 @@
using Server.Items;
using Server.Misc;
using Server.Targeting;
namespace Server.Spells.Fifth
{
public class DispelFieldSpell : MagerySpell, ISpellTargetingItem
{
private static SpellInfo m_Info = new SpellInfo(
"Dispel Field", "An Grav",
206,
9002,
Reagent.BlackPearl,
Reagent.SpidersSilk,
Reagent.SulfurousAsh,
Reagent.Garlic
);
public DispelFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override void OnCast()
{
Caster.Target = new SpellTargetItem(this, TargetFlags.None, Core.ML ? 10 : 12);
}
public void Target(Item item)
{
if (item == null)
Caster.SendLocalizedMessage(1005049); // That cannot be dispelled.
else if (!Caster.CanSee(item))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (!item.GetType().IsDefined(typeof(DispellableFieldAttribute), false))
Caster.SendLocalizedMessage(1005049); // That cannot be dispelled.
else if (item is Moongate moongate && !moongate.Dispellable)
Caster.SendLocalizedMessage(1005047); // That magic is too chaotic
else if (CheckSequence())
{
SpellHelper.Turn(Caster, item);
Effects.SendLocationParticles(EffectItem.Create(item.Location, item.Map, EffectItem.DefaultDuration), 0x376A,
9, 20, 5042);
Effects.PlaySound(item.GetWorldLocation(), item.Map, 0x201);
item.Delete();
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using Server.Factions;
using Server.Items;
using Server.Mobiles;
using Server.Spells.Seventh;
namespace Server.Spells.Fifth
{
public class IncognitoSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Incognito", "Kal In Ex",
206,
9002,
Reagent.Bloodmoss,
Reagent.Garlic,
Reagent.Nightshade
);
private static Dictionary<Mobile, InternalTimer> m_Timers = new Dictionary<Mobile, InternalTimer>();
public IncognitoSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override bool CheckCast()
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1010445); // You cannot incognito if you have a sigil
return false;
}
if (!Caster.CanBeginAction<IncognitoSpell>())
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
return false;
}
if (Caster.BodyMod == 183 || Caster.BodyMod == 184)
{
Caster.SendLocalizedMessage(1042402); // You cannot use incognito while wearing body paint
return false;
}
return true;
}
public override void OnCast()
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1010445); // You cannot incognito if you have a sigil
}
else if (!Caster.CanBeginAction<IncognitoSpell>())
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
}
else if (Caster.BodyMod == 183 || Caster.BodyMod == 184)
{
Caster.SendLocalizedMessage(1042402); // You cannot use incognito while wearing body paint
}
else if (DisguiseTimers.IsDisguised(Caster))
{
Caster.SendLocalizedMessage(1061631); // You can't do that while disguised.
}
else if (!Caster.CanBeginAction<PolymorphSpell>() || Caster.IsBodyMod)
{
DoFizzle();
}
else if (CheckSequence())
{
if (Caster.BeginAction<IncognitoSpell>())
{
DisguiseTimers.StopTimer(Caster);
Caster.HueMod = Caster.Race.RandomSkinHue();
Caster.NameMod = Caster.Female ? NameList.RandomName("female") : NameList.RandomName("male");
PlayerMobile pm = Caster as PlayerMobile;
if (pm?.Race != null)
{
pm.SetHairMods(pm.Race.RandomHair(pm.Female), pm.Race.RandomFacialHair(pm.Female));
pm.HairHue = pm.Race.RandomHairHue();
pm.FacialHairHue = pm.Race.RandomHairHue();
}
Caster.FixedParticles(0x373A, 10, 15, 5036, EffectLayer.Head);
Caster.PlaySound(0x3BD);
BaseArmor.ValidateMobile(Caster);
BaseClothing.ValidateMobile(Caster);
StopTimer(Caster);
int timeVal = 6 * Caster.Skills.Magery.Fixed / 50 + 1;
if (timeVal > 144)
timeVal = 144;
TimeSpan length = TimeSpan.FromSeconds(timeVal);
InternalTimer t = new InternalTimer(Caster, length);
m_Timers[Caster] = t;
t.Start();
BuffInfo.AddBuff(Caster, new BuffInfo(BuffIcon.Incognito, 1075819, length, Caster));
}
else
{
Caster.SendLocalizedMessage(1079022); // You're already incognitoed!
}
}
FinishSequence();
}
public static void StopTimer(Mobile m)
{
if (!m_Timers.TryGetValue(m, out InternalTimer t))
return;
t.Stop();
m_Timers.Remove(m);
BuffInfo.RemoveBuff(m, BuffIcon.Incognito);
}
private class InternalTimer : Timer
{
private Mobile m_Owner;
public InternalTimer(Mobile owner, TimeSpan length) : base(length)
{
m_Owner = owner;
/*
int val = ((6 * owner.Skills.Magery.Fixed) / 50) + 1;
if ( val > 144 )
val = 144;
Delay = TimeSpan.FromSeconds( val );
* */
Priority = TimerPriority.OneSecond;
}
protected override void OnTick()
{
if (m_Owner.CanBeginAction<IncognitoSpell>())
return;
(m_Owner as PlayerMobile)?.SetHairMods(-1, -1);
m_Owner.BodyMod = 0;
m_Owner.HueMod = -1;
m_Owner.NameMod = null;
m_Owner.EndAction<IncognitoSpell>();
BaseArmor.ValidateMobile(m_Owner);
BaseClothing.ValidateMobile(m_Owner);
}
}
}
}

View file

@ -0,0 +1,145 @@
using System.Collections.Generic;
namespace Server.Spells.Fifth
{
public class MagicReflectSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Magic Reflection", "In Jux Sanct",
242,
9012,
Reagent.Garlic,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
private static Dictionary<Mobile, ResistanceMod[]> m_Table = new Dictionary<Mobile, ResistanceMod[]>();
public MagicReflectSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override bool CheckCast()
{
if (Core.AOS)
return true;
if (Caster.MagicDamageAbsorb > 0)
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
return false;
}
if (!Caster.CanBeginAction<DefensiveSpell>())
{
Caster.SendLocalizedMessage(1005385); // The spell will not adhere to you at this time.
return false;
}
return true;
}
public override void OnCast()
{
if (Core.AOS)
{
/* The magic reflection spell decreases the caster's physical resistance, while increasing the caster's elemental resistances.
* Physical decrease = 25 - (Inscription/20).
* Elemental resistance = +10 (-20 physical, +10 elemental at GM Inscription)
* The magic reflection spell has an indefinite duration, becoming active when cast, and deactivated when re-cast.
* Reactive Armor, Protection, and Magic Reflection will stay on<EFBFBD>even after logging out, even after dying<EFBFBD>until you <EFBFBD>turn them off<EFBFBD> by casting them again.
*/
if (CheckSequence())
{
Mobile targ = Caster;
if (!m_Table.TryGetValue(targ, out ResistanceMod[] mods))
{
targ.PlaySound(0x1E9);
targ.FixedParticles(0x375A, 10, 15, 5037, EffectLayer.Waist);
int physiMod = -25 + (int)(targ.Skills.Inscribe.Value / 20);
int otherMod = 10;
mods = new[]
{
new ResistanceMod(ResistanceType.Physical, physiMod),
new ResistanceMod(ResistanceType.Fire, otherMod),
new ResistanceMod(ResistanceType.Cold, otherMod),
new ResistanceMod(ResistanceType.Poison, otherMod),
new ResistanceMod(ResistanceType.Energy, otherMod)
};
m_Table[targ] = mods;
for (int i = 0; i < mods.Length; ++i)
targ.AddResistanceMod(mods[i]);
string buffFormat = $"{physiMod}\t+{otherMod}\t+{otherMod}\t+{otherMod}\t+{otherMod}";
BuffInfo.AddBuff(targ, new BuffInfo(BuffIcon.MagicReflection, 1075817, buffFormat, true));
}
else
{
targ.PlaySound(0x1ED);
targ.FixedParticles(0x375A, 10, 15, 5037, EffectLayer.Waist);
m_Table.Remove(targ);
for (int i = 0; i < mods.Length; ++i)
targ.RemoveResistanceMod(mods[i]);
BuffInfo.RemoveBuff(targ, BuffIcon.MagicReflection);
}
}
FinishSequence();
}
else
{
if (Caster.MagicDamageAbsorb > 0)
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
}
else if (!Caster.CanBeginAction<DefensiveSpell>())
{
Caster.SendLocalizedMessage(1005385); // The spell will not adhere to you at this time.
}
else if (CheckSequence())
{
if (Caster.BeginAction<DefensiveSpell>())
{
int value = (int)(Caster.Skills.Magery.Value + Caster.Skills.Inscribe.Value);
value = (int)(8 + value / 200.0 * 7.0); //absorb from 8 to 15 "circles"
Caster.MagicDamageAbsorb = value;
Caster.FixedParticles(0x375A, 10, 15, 5037, EffectLayer.Waist);
Caster.PlaySound(0x1E9);
}
else
{
Caster.SendLocalizedMessage(1005385); // The spell will not adhere to you at this time.
}
}
FinishSequence();
}
}
public static void EndReflect(Mobile m)
{
if (!m_Table.TryGetValue(m, out ResistanceMod[] mods))
return;
for (int i = 0; i < mods?.Length; ++i)
m.RemoveResistanceMod(mods[i]);
m_Table.Remove(m);
BuffInfo.RemoveBuff(m, BuffIcon.MagicReflection);
}
}
}

View file

@ -0,0 +1,127 @@
using System;
using Server.Targeting;
namespace Server.Spells.Fifth
{
public class MindBlastSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Mind Blast", "Por Corp Wis",
218,
Core.AOS ? 9002 : 9032,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.Nightshade,
Reagent.SulfurousAsh
);
public MindBlastSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
if (Core.AOS)
m_Info.LeftHandEffect = m_Info.RightHandEffect = 9002;
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override bool DelayedDamage => !Core.AOS;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
private void AosDelay_Callback(Mobile caster, Mobile target, Mobile defender, int damage)
{
if (caster.HarmfulCheck(defender))
{
SpellHelper.Damage(this, target, Utility.RandomMinMax(damage, damage + 4), 0, 0, 100, 0, 0);
target.FixedParticles(0x374A, 10, 15, 5038, 1181, 2, EffectLayer.Head);
target.PlaySound(0x213);
}
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (Core.AOS)
{
if (Caster.CanBeHarmful(m) && CheckSequence())
{
Mobile from = Caster, target = m;
SpellHelper.Turn(from, target);
SpellHelper.CheckReflect((int)Circle, ref from, ref target);
int damage = (int)((Caster.Skills.Magery.Value + Caster.Int) / 5);
if (damage > 60)
damage = 60;
Timer.DelayCall(TimeSpan.FromSeconds(1.0),
() => AosDelay_Callback(Caster, target, m, damage));
}
}
else if (CheckHSequence(m))
{
Mobile from = Caster, target = m;
SpellHelper.Turn(from, target);
SpellHelper.CheckReflect((int)Circle, ref from, ref target);
// Algorithm: (highestStat - lowestStat) / 2 [- 50% if resisted]
int highestStat = target.Str, lowestStat = target.Str;
if (target.Dex > highestStat)
highestStat = target.Dex;
if (target.Dex < lowestStat)
lowestStat = target.Dex;
if (target.Int > highestStat)
highestStat = target.Int;
if (target.Int < lowestStat)
lowestStat = target.Int;
if (highestStat > 150)
highestStat = 150;
if (lowestStat > 150)
lowestStat = 150;
double damage = GetDamageScalar(m) * (highestStat - lowestStat) / 2; // Many users prefer 3 or 4
if (damage > 45)
damage = 45;
if (CheckResisted(target))
{
damage /= 2;
target.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
from.FixedParticles(0x374A, 10, 15, 2038, EffectLayer.Head);
target.FixedParticles(0x374A, 10, 15, 5038, EffectLayer.Head);
target.PlaySound(0x213);
SpellHelper.Damage(this, target, damage, 0, 0, 100, 0, 0);
}
FinishSequence();
}
public override double GetSlayerDamageScalar(Mobile target)
{
return 1.0; //This spell isn't affected by slayer spellbooks
}
}
}

View file

@ -0,0 +1,92 @@
using System;
using Server.Mobiles;
using Server.Spells.Chivalry;
using Server.Targeting;
namespace Server.Spells.Fifth
{
public class ParalyzeSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Paralyze", "An Ex Por",
218,
9012,
Reagent.Garlic,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
public ParalyzeSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (Core.AOS && (m.Frozen || m.Paralyzed ||
m.Spell != null && m.Spell.IsCasting && !(m.Spell is PaladinSpell)))
{
Caster.SendLocalizedMessage(1061923); // The target is already frozen.
}
else if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
double duration;
if (Core.AOS)
{
int secs = (int)(GetDamageSkill(Caster) / 10 - GetResistSkill(m) / 10);
if (!Core.SE)
secs += 2;
if (!m.Player)
secs *= 3;
if (secs < 0)
secs = 0;
duration = secs;
}
else
{
// Algorithm: ((20% of magery) + 7) seconds [- 50% if resisted]
duration = 7.0 + Caster.Skills.Magery.Value * 0.2;
if (CheckResisted(m))
duration *= 0.75;
}
if (m is PlagueBeastLord lord)
{
lord.OnParalyzed(Caster);
duration = 120;
}
m.Paralyze(TimeSpan.FromSeconds(duration));
m.PlaySound(0x204);
m.FixedEffect(0x376A, 6, 1);
HarmfulSpell(m);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Fifth
{
public class PoisonFieldSpell : MagerySpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Poison Field", "In Nox Grav",
230,
9052,
false,
Reagent.BlackPearl,
Reagent.Nightshade,
Reagent.SpidersSilk
);
public PoisonFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, Core.ML ? 10 : 12, false);
}
public void Target(IPoint3D p)
{
if (!Caster.CanSee(p))
{
Caster.SendLocalizedMessage(500237); // Target can not be seen.
}
else if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
int dx = Caster.Location.X - p.X;
int dy = Caster.Location.Y - p.Y;
int rx = (dx - dy) * 44;
int ry = (dx + dy) * 44;
bool eastToWest;
if (rx >= 0 && ry >= 0)
eastToWest = false;
else if (rx >= 0)
eastToWest = true;
else if (ry >= 0)
eastToWest = true;
else
eastToWest = false;
Effects.PlaySound(p, Caster.Map, 0x20B);
int itemID = eastToWest ? 0x3915 : 0x3922;
TimeSpan duration = TimeSpan.FromSeconds(3 + Caster.Skills.Magery.Fixed / 25);
for (int i = -2; i <= 2; ++i)
{
Point3D loc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z);
new InternalItem(itemID, loc, Caster, Caster.Map, duration, i);
}
}
FinishSequence();
}
[DispellableField]
public class InternalItem : Item
{
private Mobile m_Caster;
private DateTime m_End;
private Timer m_Timer;
public InternalItem(int itemID, Point3D loc, Mobile caster, Map map, TimeSpan duration, int val) : base(itemID)
{
bool canFit = SpellHelper.AdjustField(ref loc, map, 12, false);
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
m_Caster = caster;
m_End = DateTime.UtcNow + duration;
m_Timer = new InternalTimer(this, TimeSpan.FromSeconds(Math.Abs(val) * 0.2), caster.InLOS(this), canFit);
m_Timer.Start();
}
public InternalItem(Serial serial) : base(serial)
{
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Timer?.Stop();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
writer.Write(m_Caster);
writer.WriteDeltaTime(m_End);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 1:
{
m_Caster = reader.ReadMobile();
goto case 0;
}
case 0:
{
m_End = reader.ReadDeltaTime();
m_Timer = new InternalTimer(this, TimeSpan.Zero, true, true);
m_Timer.Start();
break;
}
}
}
public void ApplyPoisonTo(Mobile m)
{
if (m_Caster == null)
return;
Poison p;
if (Core.AOS)
{
int total = (m_Caster.Skills.Magery.Fixed + m_Caster.Skills.Poisoning.Fixed) / 2;
if (total >= 1000)
p = Poison.Deadly;
else if (total > 850)
p = Poison.Greater;
else if (total > 650)
p = Poison.Regular;
else
p = Poison.Lesser;
}
else
{
p = Poison.Regular;
}
if (m.ApplyPoison(m_Caster, p) == ApplyPoisonResult.Poisoned)
if (SpellHelper.CanRevealCaster(m))
m_Caster.RevealingAction();
(m as BaseCreature)?.OnHarmfulSpell(m_Caster);
}
public override bool OnMoveOver(Mobile m)
{
if (Visible && m_Caster != null && (!Core.AOS || m != m_Caster) &&
SpellHelper.ValidIndirectTarget(m_Caster, m) && m_Caster.CanBeHarmful(m, false))
{
m_Caster.DoHarmful(m);
ApplyPoisonTo(m);
m.PlaySound(0x474);
}
return true;
}
private class InternalTimer : Timer
{
private static Queue<Mobile> m_Queue = new Queue<Mobile>();
private bool m_InLOS, m_CanFit;
private InternalItem m_Item;
public InternalTimer(InternalItem item, TimeSpan delay, bool inLOS, bool canFit) : base(delay,
TimeSpan.FromSeconds(1.5))
{
m_Item = item;
m_InLOS = inLOS;
m_CanFit = canFit;
Priority = TimerPriority.FiftyMS;
}
protected override void OnTick()
{
if (m_Item.Deleted)
return;
if (!m_Item.Visible)
{
if (m_InLOS && m_CanFit)
m_Item.Visible = true;
else
m_Item.Delete();
if (!m_Item.Deleted)
{
m_Item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(m_Item.Location, m_Item.Map, EffectItem.DefaultDuration), 0x376A, 9, 10,
5040);
}
}
else if (DateTime.UtcNow > m_Item.m_End)
{
m_Item.Delete();
Stop();
}
else
{
Map map = m_Item.Map;
Mobile caster = m_Item.m_Caster;
if (map != null && caster != null)
{
bool eastToWest = m_Item.ItemID == 0x3915;
IPooledEnumerable<Mobile> eable = map.GetMobilesInBounds(
new Rectangle2D(m_Item.X - (eastToWest ? 0 : 1), m_Item.Y - (eastToWest ? 1 : 0),
eastToWest ? 1 : 2, eastToWest ? 2 : 1));
foreach (Mobile m in eable)
if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
m_Queue.Enqueue(m);
eable.Free();
while (m_Queue.Count > 0)
{
Mobile m = m_Queue.Dequeue();
caster.DoHarmful(m);
m_Item.ApplyPoisonTo(m);
m.PlaySound(0x474);
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,95 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Fifth
{
public class SummonCreatureSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Summon Creature", "Kal Xen",
16,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
// NOTE: Creature list based on 1hr of summon/release on OSI.
private static Type[] m_Types =
{
typeof(PolarBear),
typeof(GrizzlyBear),
typeof(BlackBear),
typeof(Horse),
typeof(Walrus),
typeof(Chicken),
typeof(Scorpion),
typeof(GiantSerpent),
typeof(Llama),
typeof(Alligator),
typeof(GreyWolf),
typeof(Slime),
typeof(Eagle),
typeof(Gorilla),
typeof(SnowLeopard),
typeof(Pig),
typeof(Hind),
typeof(Rabbit)
};
public SummonCreatureSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fifth;
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 2 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public override void OnCast()
{
if (CheckSequence())
try
{
BaseCreature creature = (BaseCreature)Activator.CreateInstance(m_Types[Utility.Random(m_Types.Length)]);
//creature.ControlSlots = 2;
TimeSpan duration;
if (Core.AOS)
duration = TimeSpan.FromSeconds(2 * Caster.Skills.Magery.Fixed / 5);
else
duration = TimeSpan.FromSeconds(4.0 * Caster.Skills.Magery.Value);
SpellHelper.Summon(creature, Caster, 0x215, duration, false, false);
}
catch
{
// ignored
}
FinishSequence();
}
public override TimeSpan GetCastDelay()
{
if (Core.AOS)
return TimeSpan.FromTicks(base.GetCastDelay().Ticks * 5);
return base.GetCastDelay() + TimeSpan.FromSeconds(6.0);
}
}
}

View file

@ -0,0 +1,62 @@
using System;
using Server.Targeting;
namespace Server.Spells.First
{
public class ClumsySpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Clumsy", "Uus Jux",
212,
9031,
Reagent.Bloodmoss,
Reagent.Nightshade
);
public ClumsySpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
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))
{
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
SpellHelper.AddStatCurse(Caster, m, StatType.Dex);
m.Spell?.OnCasterHurt();
m.Paralyzed = false;
m.FixedParticles(0x3779, 10, 15, 5002, EffectLayer.Head);
m.PlaySound(0x1DF);
int percentage = (int)(SpellHelper.GetOffsetScalar(Caster, m, true) * 100);
TimeSpan length = SpellHelper.GetDuration(Caster, m);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.Clumsy, 1075831, length, m, percentage.ToString()));
HarmfulSpell(m);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,88 @@
using System;
using Server.Items;
namespace Server.Spells.First
{
public class CreateFoodSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Create Food", "In Mani Ylem",
224,
9011,
Reagent.Garlic,
Reagent.Ginseng,
Reagent.MandrakeRoot
);
private static FoodInfo[] m_Food =
{
new FoodInfo(typeof(Grapes), "a grape bunch"),
new FoodInfo(typeof(Ham), "a ham"),
new FoodInfo(typeof(CheeseWedge), "a wedge of cheese"),
new FoodInfo(typeof(Muffins), "muffins"),
new FoodInfo(typeof(FishSteak), "a fish steak"),
new FoodInfo(typeof(Ribs), "cut of ribs"),
new FoodInfo(typeof(CookedBird), "a cooked bird"),
new FoodInfo(typeof(Sausage), "sausage"),
new FoodInfo(typeof(Apple), "an apple"),
new FoodInfo(typeof(Peach), "a peach")
};
public CreateFoodSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override void OnCast()
{
if (CheckSequence())
{
FoodInfo foodInfo = m_Food[Utility.Random(m_Food.Length)];
Item food = foodInfo.Create();
if (food != null)
{
Caster.AddToBackpack(food);
// You magically create food in your backpack:
Caster.SendLocalizedMessage(1042695, true, " " + foodInfo.Name);
Caster.FixedParticles(0, 10, 5, 2003, EffectLayer.RightHand);
Caster.PlaySound(0x1E2);
}
}
FinishSequence();
}
}
public class FoodInfo
{
public FoodInfo(Type type, string name)
{
Type = type;
Name = name;
}
public Type Type{ get; set; }
public string Name{ get; set; }
public Item Create()
{
Item item;
try
{
item = (Item)Activator.CreateInstance(Type);
}
catch
{
item = null;
}
return item;
}
}
}

View file

@ -0,0 +1,60 @@
using System;
using Server.Targeting;
namespace Server.Spells.First
{
public class FeeblemindSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Feeblemind", "Rel Wis",
212,
9031,
Reagent.Ginseng,
Reagent.Nightshade
);
public FeeblemindSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
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))
{
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
SpellHelper.AddStatCurse(Caster, m, StatType.Int);
m.Spell?.OnCasterHurt();
m.Paralyzed = false;
m.FixedParticles(0x3779, 10, 15, 5004, EffectLayer.Head);
m.PlaySound(0x1E4);
int percentage = (int)(SpellHelper.GetOffsetScalar(Caster, m, true) * 100);
TimeSpan length = SpellHelper.GetDuration(Caster, m);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.FeebleMind, 1075833, length, m, percentage.ToString()));
HarmfulSpell(m);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,97 @@
using Server.Engines.ConPVP;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Spells.First
{
public class HealSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Heal", "In Mani",
224,
9061,
Reagent.Garlic,
Reagent.Ginseng,
Reagent.SpidersSilk
);
public HealSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
{
Caster.SendMessage(0x22, "You cannot cast this spell when in sudden death.");
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
{
Caster.SendLocalizedMessage(500237); // Target can not be seen.
}
else if (m.IsDeadBondedPet)
{
Caster.SendLocalizedMessage(1060177); // You cannot heal a creature that is already dead!
}
else if (m is BaseCreature creature && creature.IsAnimatedDead)
{
Caster.SendLocalizedMessage(1061654); // You cannot heal that which is not alive.
}
else if (m is Golem)
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x3B2, 500951); // You cannot heal that.
}
else if (m.Poisoned || MortalStrike.IsWounded(m))
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x22, Caster == m ? 1005000 : 1010398);
}
else if (CheckBSequence(m))
{
SpellHelper.Turn(Caster, m);
int toHeal;
if (Core.AOS)
{
toHeal = Caster.Skills.Magery.Fixed / 120;
toHeal += Utility.RandomMinMax(1, 4);
if (Core.SE && Caster != m)
toHeal = (int)(toHeal * 1.5);
}
else
{
toHeal = (int)(Caster.Skills.Magery.Value * 0.1);
toHeal += Utility.Random(1, 5);
}
//m.Heal( toHeal, Caster );
SpellHelper.Heal(toHeal, m, Caster);
m.FixedParticles(0x376A, 9, 32, 5005, EffectLayer.Waist);
m.PlaySound(0x1F2);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,73 @@
using Server.Targeting;
namespace Server.Spells.First
{
public class MagicArrowSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Magic Arrow", "In Por Ylem",
212,
9041,
Reagent.SulfurousAsh
);
public MagicArrowSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override bool DelayedDamageStacking => !Core.AOS;
public override bool DelayedDamage => true;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
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))
{
Mobile source = Caster;
SpellHelper.Turn(source, m);
SpellHelper.CheckReflect((int)Circle, ref source, ref m);
double damage;
if (Core.AOS)
{
damage = GetNewAosDamage(10, 1, 4, m);
}
else
{
damage = Utility.Random(4, 4);
if (CheckResisted(m))
{
damage *= 0.75;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
damage *= GetDamageScalar(m);
}
source.MovingParticles(m, 0x36E4, 5, 0, false, false, 3006, 0, 0);
source.PlaySound(0x1E5);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,75 @@
using Server.Targeting;
namespace Server.Spells.First
{
public class NightSightSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Night Sight", "In Lor",
236,
9031,
Reagent.SulfurousAsh,
Reagent.SpidersSilk
);
public NightSightSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override void OnCast()
{
Caster.Target = new NightSightTarget(this);
}
private class NightSightTarget : Target
{
private Spell m_Spell;
public NightSightTarget(Spell spell) : base(12, false, TargetFlags.Beneficial)
{
m_Spell = spell;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile targ && m_Spell.CheckBSequence(targ))
{
SpellHelper.Turn(m_Spell.Caster, targ);
if (targ.BeginAction<LightCycle>())
{
new LightCycle.NightSightTimer(targ).Start();
int level = (int)(LightCycle.DungeonLevel *
((Core.AOS
? targ.Skills.Magery.Value
: from.Skills.Magery.Value) / 100));
if (level < 0)
level = 0;
targ.LightLevel = level;
targ.FixedParticles(0x376A, 9, 32, 5007, EffectLayer.Waist);
targ.PlaySound(0x1E3);
BuffInfo.AddBuff(targ,
new BuffInfo(BuffIcon.NightSight, 1075643)); //Night Sight/You ignore lighting effects
}
else
{
from.SendMessage("{0} already have nightsight.", from == targ ? "You" : "They");
}
}
m_Spell.FinishSequence();
}
protected override void OnTargetFinish(Mobile from)
{
m_Spell.FinishSequence();
}
}
}
}

View file

@ -0,0 +1,151 @@
using System.Collections.Generic;
namespace Server.Spells.First
{
public class ReactiveArmorSpell : MagerySpell
{
private static SpellInfo m_Info = new SpellInfo(
"Reactive Armor", "Flam Sanct",
236,
9011,
Reagent.Garlic,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
private static Dictionary<Mobile, ResistanceMod[]> m_Table = new Dictionary<Mobile, ResistanceMod[]>();
public ReactiveArmorSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override bool CheckCast()
{
if (Core.AOS)
return true;
if (Caster.MeleeDamageAbsorb > 0)
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
return false;
}
if (!Caster.CanBeginAction<DefensiveSpell>())
{
Caster.SendLocalizedMessage(1005385); // The spell will not adhere to you at this time.
return false;
}
return true;
}
public override void OnCast()
{
if (Core.AOS)
{
/* The reactive armor spell increases the caster's physical resistance, while lowering the caster's elemental resistances.
* 15 + (Inscription/20) Physcial bonus
* -5 Elemental
* The reactive armor spell has an indefinite duration, becoming active when cast, and deactivated when re-cast.
* Reactive Armor, Protection, and Magic Reflection will stay on<EFBFBD>even after logging out, even after dying<EFBFBD>until you <EFBFBD>turn them off<EFBFBD> by casting them again.
* (+20 physical -5 elemental at 100 Inscription)
*/
if (CheckSequence())
{
Mobile targ = Caster;
if (!m_Table.TryGetValue(targ, out ResistanceMod[] mods))
{
targ.PlaySound(0x1E9);
targ.FixedParticles(0x376A, 9, 32, 5008, EffectLayer.Waist);
mods = new []
{
new ResistanceMod(ResistanceType.Physical,
15 + (int)(targ.Skills.Inscribe.Value / 20)),
new ResistanceMod(ResistanceType.Fire, -5),
new ResistanceMod(ResistanceType.Cold, -5),
new ResistanceMod(ResistanceType.Poison, -5),
new ResistanceMod(ResistanceType.Energy, -5)
};
m_Table[targ] = mods;
for (int i = 0; i < mods.Length; ++i)
targ.AddResistanceMod(mods[i]);
int physresist = 15 + (int)(targ.Skills.Inscribe.Value / 20);
string args = $"{physresist}\t{5}\t{5}\t{5}\t{5}";
BuffInfo.AddBuff(Caster, new BuffInfo(BuffIcon.ReactiveArmor, 1075812, 1075813, args));
}
else
{
targ.PlaySound(0x1ED);
targ.FixedParticles(0x376A, 9, 32, 5008, EffectLayer.Waist);
m_Table.Remove(targ);
for (int i = 0; i < mods.Length; ++i)
targ.RemoveResistanceMod(mods[i]);
BuffInfo.RemoveBuff(Caster, BuffIcon.ReactiveArmor);
}
}
FinishSequence();
}
else
{
if (Caster.MeleeDamageAbsorb > 0)
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
}
else if (!Caster.CanBeginAction<DefensiveSpell>())
{
Caster.SendLocalizedMessage(1005385); // The spell will not adhere to you at this time.
}
else if (CheckSequence())
{
if (Caster.BeginAction<DefensiveSpell>())
{
int value = (int)(Caster.Skills.Magery.Value + Caster.Skills.Meditation.Value +
Caster.Skills.Inscribe.Value);
value /= 3;
if (value < 0)
value = 1;
else if (value > 75)
value = 75;
Caster.MeleeDamageAbsorb = value;
Caster.FixedParticles(0x376A, 9, 32, 5008, EffectLayer.Waist);
Caster.PlaySound(0x1F2);
}
else
{
Caster.SendLocalizedMessage(1005385); // The spell will not adhere to you at this time.
}
}
FinishSequence();
}
}
public static void EndArmor(Mobile m)
{
if (!m_Table.TryGetValue(m, out ResistanceMod[] mods))
return;
for (int i = 0; i < mods?.Length; ++i)
m.RemoveResistanceMod(mods[i]);
m_Table.Remove(m);
BuffInfo.RemoveBuff(m, BuffIcon.ReactiveArmor);
}
}
}

View file

@ -0,0 +1,60 @@
using System;
using Server.Targeting;
namespace Server.Spells.First
{
public class WeakenSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Weaken", "Des Mani",
212,
9031,
Reagent.Garlic,
Reagent.Nightshade
);
public WeakenSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
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))
{
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
SpellHelper.AddStatCurse(Caster, m, StatType.Str);
m.Spell?.OnCasterHurt();
m.Paralyzed = false;
m.FixedParticles(0x3779, 10, 15, 5009, EffectLayer.Waist);
m.PlaySound(0x1E6);
int percentage = (int)(SpellHelper.GetOffsetScalar(Caster, m, true) * 100);
TimeSpan length = SpellHelper.GetDuration(Caster, m);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.Weaken, 1075837, length, m, percentage.ToString()));
HarmfulSpell(m);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Fourth
{
public class ArchCureSpell : MagerySpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Arch Cure", "Vas An Nox",
215,
9061,
Reagent.Garlic,
Reagent.Ginseng,
Reagent.MandrakeRoot
);
public ArchCureSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fourth;
// Arch cure is now 1/4th of a second faster
public override TimeSpan CastDelayBase => base.CastDelayBase - TimeSpan.FromSeconds(0.25);
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, Core.ML ? 10 : 12);
}
public void Target(IPoint3D p)
{
if (!Caster.CanSee(p))
{
Caster.SendLocalizedMessage(500237); // Target can not be seen.
}
else if (CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
List<Mobile> targets = new List<Mobile>();
Map map = Caster.Map;
Mobile directTarget = p as Mobile;
if (map != null)
{
bool feluccaRules = map.Rules == MapRules.FeluccaRules;
// You can target any living mobile directly, beneficial checks apply
if (directTarget != null && Caster.CanBeBeneficial(directTarget, false))
targets.Add(directTarget);
IPooledEnumerable<Mobile> eable = map.GetMobilesInRange(new Point3D(p), 2);
targets.AddRange(eable.Where(m => m != directTarget).Where(m => AreaCanTarget(m, feluccaRules)));
eable.Free();
}
Effects.PlaySound(p, Caster.Map, 0x299);
if (targets.Count > 0)
{
int cured = 0;
for (int i = 0; i < targets.Count; ++i)
{
Mobile m = targets[i];
Caster.DoBeneficial(m);
Poison poison = m.Poison;
if (poison != null)
{
int chanceToCure = 10000 + (int)(Caster.Skills.Magery.Value * 75) -
(poison.Level + 1) * 1750;
chanceToCure /= 100;
chanceToCure -= 1;
if (chanceToCure > Utility.Random(100) && m.CurePoison(Caster))
++cured;
}
m.FixedParticles(0x373A, 10, 15, 5012, EffectLayer.Waist);
m.PlaySound(0x1E0);
}
if (cured > 0)
Caster.SendLocalizedMessage(1010058); // You have cured the target of all poisons!
}
}
FinishSequence();
}
private bool AreaCanTarget(Mobile target, bool feluccaRules)
{
/* Arch cure area effect won't cure aggressors, victims, murderers, criminals or monsters.
* In Felucca, it will also not cure summons and pets.
* For red players it will only cure themselves and guild members.
*/
if (!Caster.CanBeBeneficial(target, false))
return false;
if (Core.AOS && target != Caster)
{
if (IsAggressor(target) || IsAggressed(target))
return false;
if ((!IsInnocentTo(Caster, target) || !IsInnocentTo(target, Caster)) && !IsAllyTo(Caster, target))
return false;
if (feluccaRules && !(target is PlayerMobile))
return false;
}
return true;
}
private bool IsAggressor(Mobile m)
{
foreach (AggressorInfo info in Caster.Aggressors)
if (m == info.Attacker && !info.Expired)
return true;
return false;
}
private bool IsAggressed(Mobile m)
{
foreach (AggressorInfo info in Caster.Aggressed)
if (m == info.Defender && !info.Expired)
return true;
return false;
}
private static bool IsInnocentTo(Mobile from, Mobile to)
{
return Notoriety.Compute(from, to) == Notoriety.Innocent;
}
private static bool IsAllyTo(Mobile from, Mobile to)
{
return Notoriety.Compute(from, to) == Notoriety.Ally;
}
}
}

View file

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Engines.PartySystem;
using Server.Spells.Second;
using Server.Targeting;
namespace Server.Spells.Fourth
{
public class ArchProtectionSpell : MagerySpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Arch Protection", "Vas Uus Sanct",
Core.AOS ? 239 : 215,
9011,
Reagent.Garlic,
Reagent.Ginseng,
Reagent.MandrakeRoot,
Reagent.SulfurousAsh
);
private static Dictionary<Mobile, int> _Table = new Dictionary<Mobile, int>();
public ArchProtectionSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fourth;
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, Core.ML ? 10 : 12);
}
public void Target(IPoint3D p)
{
if (!Caster.CanSee(p))
{
Caster.SendLocalizedMessage(500237); // Target can not be seen.
}
else if (CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
if (!Core.AOS)
Effects.PlaySound(p, Caster.Map, 0x299);
if (Caster.Map == null)
{
FinishSequence();
return;
}
IEnumerable<Mobile> targets = Caster.Map.GetMobilesInRange(new Point3D(p), Core.AOS ? 2 : 3)
.Where(m => Caster.CanBeBeneficial(m, false));
if (Core.AOS)
{
Party party = Party.Get(Caster);
foreach (Mobile m in targets)
{
if (m == Caster || party?.Contains(m) == true)
{
Caster.DoBeneficial(m);
ProtectionSpell.Toggle(Caster, m);
}
}
}
else
{
int val = (int)(Caster.Skills.Magery.Value / 10.0 + 1);
foreach (Mobile m in targets)
{
if (m.BeginAction<ArchProtectionSpell>())
{
Caster.DoBeneficial(m);
m.VirtualArmorMod += val;
AddEntry(m, val);
new InternalTimer(m, Caster).Start();
m.FixedParticles(0x375A, 9, 20, 5027, EffectLayer.Waist);
m.PlaySound(0x1F7);
}
}
}
}
FinishSequence();
}
private static void AddEntry(Mobile m, int v)
{
_Table[m] = v;
}
public static void RemoveEntry(Mobile m)
{
if (_Table.TryGetValue(m, out int v))
{
_Table.Remove(m);
m.EndAction<ArchProtectionSpell>();
m.VirtualArmorMod -= v;
if (m.VirtualArmorMod < 0)
m.VirtualArmorMod = 0;
}
}
private class InternalTimer : Timer
{
private Mobile m_Owner;
public InternalTimer(Mobile target, Mobile caster) : base(TimeSpan.FromSeconds(0))
{
double time = caster.Skills.Magery.Value * 1.2;
if (time > 144)
time = 144;
Delay = TimeSpan.FromSeconds(time);
Priority = TimerPriority.OneSecond;
m_Owner = target;
}
protected override void OnTick()
{
RemoveEntry(m_Owner);
}
}
}
}

View file

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using Server.Targeting;
namespace Server.Spells.Fourth
{
public class CurseSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Curse", "Des Sanct",
227,
9031,
Reagent.Nightshade,
Reagent.Garlic,
Reagent.SulfurousAsh
);
private static HashSet<Mobile> m_UnderEffect = new HashSet<Mobile>();
public CurseSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fourth;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public static void RemoveEffect(Mobile m)
{
m_UnderEffect.Remove(m);
m.UpdateResistances();
}
public static bool UnderEffect(Mobile m)
{
return m_UnderEffect.Contains(m);
}
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))
{
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
SpellHelper.AddStatCurse(Caster, m, StatType.Str);
SpellHelper.DisableSkillCheck = true;
SpellHelper.AddStatCurse(Caster, m, StatType.Dex);
SpellHelper.AddStatCurse(Caster, m, StatType.Int);
SpellHelper.DisableSkillCheck = false;
if (Caster.Player && m.Player /*&& Caster != m */ && !UnderEffect(m)
) //On OSI you CAN curse yourself and get this effect.
{
TimeSpan duration = SpellHelper.GetDuration(Caster, m);
m_UnderEffect.Add(m);
Timer.DelayCall(duration, RemoveEffect, m);
m.UpdateResistances();
}
m.Spell?.OnCasterHurt();
m.Paralyzed = false;
m.FixedParticles(0x374A, 10, 15, 5028, EffectLayer.Waist);
m.PlaySound(0x1E1);
int percentage = (int)(SpellHelper.GetOffsetScalar(Caster, m, true) * 100);
TimeSpan length = SpellHelper.GetDuration(Caster, m);
string args = $"{percentage}\t{percentage}\t{percentage}\t{10}\t{10}\t{10}\t{10}";
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.Curse, 1075835, 1075836, length, m, args));
HarmfulSpell(m);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,280 @@
using System;
using System.Collections;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Fourth
{
public class FireFieldSpell : MagerySpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Fire Field", "In Flam Grav",
215,
9041,
false,
Reagent.BlackPearl,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
public FireFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fourth;
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, Core.ML ? 10 : 12);
}
public void Target(IPoint3D p)
{
if (!Caster.CanSee(p))
{
Caster.SendLocalizedMessage(500237); // Target can not be seen.
}
else if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
int dx = Caster.Location.X - p.X;
int dy = Caster.Location.Y - p.Y;
int rx = (dx - dy) * 44;
int ry = (dx + dy) * 44;
bool eastToWest;
if (rx >= 0 && ry >= 0)
eastToWest = false;
else if (rx >= 0)
eastToWest = true;
else if (ry >= 0)
eastToWest = true;
else
eastToWest = false;
Effects.PlaySound(p, Caster.Map, 0x20C);
int itemID = eastToWest ? 0x398C : 0x3996;
TimeSpan duration;
if (Core.AOS)
duration = TimeSpan.FromSeconds((15 + Caster.Skills.Magery.Fixed / 5) / 4);
else
duration = TimeSpan.FromSeconds(4.0 + Caster.Skills.Magery.Value * 0.5);
for (int i = -2; i <= 2; ++i)
{
Point3D loc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z);
new FireFieldItem(itemID, loc, Caster, Caster.Map, duration, i);
}
}
FinishSequence();
}
[DispellableField]
public class FireFieldItem : Item
{
private Mobile m_Caster;
private int m_Damage;
private DateTime m_End;
private Timer m_Timer;
public FireFieldItem(int itemID, Point3D loc, Mobile caster, Map map, TimeSpan duration, int val,
int damage = 2) : base(itemID)
{
bool canFit = SpellHelper.AdjustField(ref loc, map, 12, false);
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
m_Caster = caster;
m_Damage = damage;
m_End = DateTime.UtcNow + duration;
m_Timer = new InternalTimer(this, TimeSpan.FromSeconds(Math.Abs(val) * 0.2), caster.InLOS(this), canFit);
m_Timer.Start();
}
public FireFieldItem(Serial serial) : base(serial)
{
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Timer?.Stop();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(2); // version
writer.Write(m_Damage);
writer.Write(m_Caster);
writer.WriteDeltaTime(m_End);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 2:
{
m_Damage = reader.ReadInt();
goto case 1;
}
case 1:
{
m_Caster = reader.ReadMobile();
goto case 0;
}
case 0:
{
m_End = reader.ReadDeltaTime();
m_Timer = new InternalTimer(this, TimeSpan.Zero, true, true);
m_Timer.Start();
break;
}
}
if (version < 2)
m_Damage = 2;
}
public override bool OnMoveOver(Mobile m)
{
if (Visible && m_Caster != null && (!Core.AOS || m != m_Caster) &&
SpellHelper.ValidIndirectTarget(m_Caster, m) && m_Caster.CanBeHarmful(m, false))
{
if (SpellHelper.CanRevealCaster(m))
m_Caster.RevealingAction();
m_Caster.DoHarmful(m);
int damage = m_Damage;
if (!Core.AOS && m.CheckSkill(SkillName.MagicResist, 0.0, 30.0))
{
damage = 1;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
AOS.Damage(m, m_Caster, damage, 0, 100, 0, 0, 0);
m.PlaySound(0x208);
(m as BaseCreature)?.OnHarmfulSpell(m_Caster);
}
return true;
}
private class InternalTimer : Timer
{
private static Queue m_Queue = new Queue();
private bool m_InLOS, m_CanFit;
private FireFieldItem m_Item;
public InternalTimer(FireFieldItem item, TimeSpan delay, bool inLOS, bool canFit) : base(delay,
TimeSpan.FromSeconds(1.0))
{
m_Item = item;
m_InLOS = inLOS;
m_CanFit = canFit;
Priority = TimerPriority.FiftyMS;
}
protected override void OnTick()
{
if (m_Item.Deleted)
return;
if (!m_Item.Visible)
{
if (m_InLOS && m_CanFit)
m_Item.Visible = true;
else
m_Item.Delete();
if (!m_Item.Deleted)
{
m_Item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(m_Item.Location, m_Item.Map, EffectItem.DefaultDuration), 0x376A, 9, 10,
5029);
}
}
else if (DateTime.UtcNow > m_Item.m_End)
{
m_Item.Delete();
Stop();
}
else
{
Map map = m_Item.Map;
Mobile caster = m_Item.m_Caster;
if (map == null || caster == null)
return;
foreach (Mobile m in m_Item.GetMobilesInRange(0))
if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
m_Queue.Enqueue(m);
while (m_Queue.Count > 0)
{
Mobile m = (Mobile)m_Queue.Dequeue();
if (SpellHelper.CanRevealCaster(m))
caster.RevealingAction();
caster.DoHarmful(m);
int damage = m_Item.m_Damage;
if (!Core.AOS && m.CheckSkill(SkillName.MagicResist, 0.0, 30.0))
{
damage = 1;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
AOS.Damage(m, caster, damage, 0, 100, 0, 0, 0);
m.PlaySound(0x208);
(m as BaseCreature)?.OnHarmfulSpell(caster);
}
}
}
}
}
}
}

View file

@ -0,0 +1,85 @@
using Server.Engines.ConPVP;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Spells.Fourth
{
public class GreaterHealSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Greater Heal", "In Vas Mani",
204,
9061,
Reagent.Garlic,
Reagent.Ginseng,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
public GreaterHealSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fourth;
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
{
Caster.SendMessage(0x22, "You cannot cast this spell when in sudden death.");
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (m is BaseCreature creature && creature.IsAnimatedDead)
{
Caster.SendLocalizedMessage(1061654); // You cannot heal that which is not alive.
}
else if (m.IsDeadBondedPet)
{
Caster.SendLocalizedMessage(1060177); // You cannot heal a creature that is already dead!
}
else if (m is Golem)
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x3B2, 500951); // You cannot heal that.
}
else if (m.Poisoned || MortalStrike.IsWounded(m))
{
Caster.LocalOverheadMessage(MessageType.Regular, 0x22, Caster == m ? 1005000 : 1010398);
}
else if (CheckBSequence(m))
{
SpellHelper.Turn(Caster, m);
// Algorithm: (40% of magery) + (1-10)
int toHeal = (int)(Caster.Skills.Magery.Value * 0.4);
toHeal += Utility.Random(1, 10);
//m.Heal( toHeal, Caster );
SpellHelper.Heal(toHeal, m, Caster);
m.FixedParticles(0x376A, 9, 32, 5030, EffectLayer.Waist);
m.PlaySound(0x202);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,69 @@
using Server.Targeting;
namespace Server.Spells.Fourth
{
public class LightningSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Lightning", "Por Ort Grav",
239,
9021,
Reagent.MandrakeRoot,
Reagent.SulfurousAsh
);
public LightningSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fourth;
public override bool DelayedDamage => false;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
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))
{
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
double damage;
if (Core.AOS)
{
damage = GetNewAosDamage(23, 1, 4, m);
}
else
{
damage = Utility.Random(12, 9);
if (CheckResisted(m))
{
damage *= 0.75;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
damage *= GetDamageScalar(m);
}
m.BoltEffect(0);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 100);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using Server.Targeting;
namespace Server.Spells.Fourth
{
public class ManaDrainSpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Mana Drain", "Ort Rel",
215,
9031,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.SpidersSilk
);
private static HashSet<Mobile> m_Table = new HashSet<Mobile>();
public ManaDrainSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Fourth;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
private void AosDelay_Callback(Mobile m, int mana)
{
if (m.Alive && !m.IsDeadBondedPet)
{
m.Mana += mana;
m.FixedEffect(0x3779, 10, 25);
m.PlaySound(0x28E);
}
m_Table.Remove(m);
}
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))
{
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
m.Spell?.OnCasterHurt();
m.Paralyzed = false;
if (Core.AOS)
{
int toDrain = 40 + (int)(GetDamageSkill(Caster) - GetResistSkill(m));
if (toDrain < 0)
toDrain = 0;
else if (toDrain > m.Mana)
toDrain = m.Mana;
if (m_Table.Contains(m))
toDrain = 0;
m.FixedParticles(0x3789, 10, 25, 5032, EffectLayer.Head);
m.PlaySound(0x1F8);
if (toDrain > 0)
{
m.Mana -= toDrain;
m_Table.Add(m);
Timer.DelayCall(TimeSpan.FromSeconds(5.0), () => AosDelay_Callback(m, toDrain));
}
}
else
{
if (CheckResisted(m))
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
else if (m.Mana >= 100)
m.Mana -= Utility.Random(1, 100);
else
m.Mana -= Utility.Random(1, m.Mana);
m.FixedParticles(0x374A, 10, 15, 5032, EffectLayer.Head);
m.PlaySound(0x1F8);
}
HarmfulSpell(m);
}
FinishSequence();
}
public override double GetResistPercent(Mobile target)
{
return 99.0;
}
}
}

View file

@ -0,0 +1,143 @@
using Server.Factions;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
using Server.Spells.Necromancy;
namespace Server.Spells.Fourth
{
public class RecallSpell : MagerySpell, IRecallSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Recall", "Kal Ort Por",
239,
9031,
Reagent.BlackPearl,
Reagent.Bloodmoss,
Reagent.MandrakeRoot
);
private Runebook m_Book;
private RunebookEntry m_Entry;
public RecallSpell(Mobile caster, RunebookEntry entry = null, Runebook book = null, Item scroll = null) :
base(caster, scroll, m_Info)
{
m_Entry = entry;
m_Book = book;
}
public override SpellCircle Circle => SpellCircle.Fourth;
public override void GetCastSkills(out double min, out double max)
{
if (TransformationSpellHelper.UnderTransformation(Caster, typeof(WraithFormSpell)))
min = max = 0;
else if (Core.SE && m_Book != null) //recall using Runebook charge
min = max = 0;
else
base.GetCastSkills(out min, out max);
}
public override void OnCast()
{
if (m_Entry == null)
Caster.Target = new RecallSpellTarget(this);
else
Effect(m_Entry.Location, m_Entry.Map, true);
}
public override bool CheckCast()
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
return false;
}
if (Caster.Criminal)
{
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
return false;
}
if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
return false;
}
if (WeightOverloading.IsOverloaded(Caster))
{
Caster.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
return false;
}
return SpellHelper.CheckTravel(Caster, TravelCheckType.RecallFrom);
}
public void Effect(Point3D loc, Map map, bool checkMulti)
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (map == null || !Core.AOS && Caster.Map != map)
{
Caster.SendLocalizedMessage(1005569); // You can not recall to another facet.
}
else if (!SpellHelper.CheckTravel(Caster, TravelCheckType.RecallFrom))
{
}
else if (!SpellHelper.CheckTravel(Caster, map, loc, TravelCheckType.RecallTo))
{
}
else if (map == Map.Felucca && Caster is PlayerMobile mobile && mobile.Young)
{
mobile.SendLocalizedMessage(1049543); // You decide against traveling to Felucca while you are still young.
}
else if (Caster.Kills >= 5 && map != Map.Felucca)
{
Caster.SendLocalizedMessage(1019004); // You are not allowed to travel there.
}
else if (Caster.Criminal)
{
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
}
else if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
}
else if (WeightOverloading.IsOverloaded(Caster))
{
Caster.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
}
else if (!map.CanSpawnMobile(loc.X, loc.Y, loc.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (checkMulti && SpellHelper.CheckMulti(loc, map))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (m_Book != null && m_Book.CurCharges <= 0)
{
Caster.SendLocalizedMessage(502412); // There are no charges left on that item.
}
else if (CheckSequence())
{
BaseCreature.TeleportPets(Caster, loc, map, true);
if (m_Book != null)
--m_Book.CurCharges;
Caster.PlaySound(0x1FC);
Caster.MoveToWorld(loc, map);
Caster.PlaySound(0x1FC);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,82 @@
using System;
namespace Server.Spells
{
public class FlySpell : Spell
{
private static readonly SpellInfo m_Info = new SpellInfo("Gargoyle Flight", null, -1, 9002);
private bool m_Stop;
public FlySpell(Mobile caster)
: base(caster, null, m_Info)
{
}
public override bool ClearHandsOnCast => false;
public override bool RevealOnCast => false;
public override double CastDelayFastScalar => 0;
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(.25);
public override TimeSpan GetCastRecovery()
{
return TimeSpan.Zero;
}
public override int GetMana()
{
return 0;
}
public override bool ConsumeReagents()
{
return true;
}
public override bool CheckFizzle()
{
return true;
}
public void Stop()
{
m_Stop = true;
Disturb(DisturbType.Hurt, false);
}
public override bool CheckDisturb(DisturbType type, bool checkFirst, bool resistable)
{
if (type == DisturbType.EquipRequest || type == DisturbType.UseRequest /* || type == DisturbType.Hurt*/)
return false;
return true;
}
public override void DoHurtFizzle()
{
}
public override void DoFizzle()
{
}
public override void OnDisturb(DisturbType type, bool message)
{
if (message && !m_Stop)
Caster.SendLocalizedMessage(1113192); // You have been disrupted while attempting to fly!
}
public override void OnCast()
{
Caster.Flying = false;
BuffInfo.RemoveBuff(Caster, BuffIcon.Fly);
Caster.Animate(60, 10, 1, true, false, 0);
Caster.SendLocalizedMessage(1112567); // You are flying.
Caster.Flying = true;
BuffInfo.AddBuff(Caster, new BuffInfo(BuffIcon.Fly, 1112567));
FinishSequence();
}
}
}

View file

@ -0,0 +1,207 @@
using System;
using Server.Spells.Bushido;
using Server.Spells.Chivalry;
using Server.Spells.Eighth;
using Server.Spells.Fifth;
using Server.Spells.First;
using Server.Spells.Fourth;
using Server.Spells.Mysticism;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
using Server.Spells.Second;
using Server.Spells.Seventh;
using Server.Spells.Sixth;
using Server.Spells.Spellweaving;
using Server.Spells.Third;
namespace Server.Spells
{
public class Initializer
{
public static void Initialize()
{
// First circle
Register(00, typeof(ClumsySpell));
Register(01, typeof(CreateFoodSpell));
Register(02, typeof(FeeblemindSpell));
Register(03, typeof(HealSpell));
Register(04, typeof(MagicArrowSpell));
Register(05, typeof(NightSightSpell));
Register(06, typeof(ReactiveArmorSpell));
Register(07, typeof(WeakenSpell));
// Second circle
Register(08, typeof(AgilitySpell));
Register(09, typeof(CunningSpell));
Register(10, typeof(CureSpell));
Register(11, typeof(HarmSpell));
Register(12, typeof(MagicTrapSpell));
Register(13, typeof(RemoveTrapSpell));
Register(14, typeof(ProtectionSpell));
Register(15, typeof(StrengthSpell));
// Third circle
Register(16, typeof(BlessSpell));
Register(17, typeof(FireballSpell));
Register(18, typeof(MagicLockSpell));
Register(19, typeof(PoisonSpell));
Register(20, typeof(TelekinesisSpell));
Register(21, typeof(TeleportSpell));
Register(22, typeof(UnlockSpell));
Register(23, typeof(WallOfStoneSpell));
// Fourth circle
Register(24, typeof(ArchCureSpell));
Register(25, typeof(ArchProtectionSpell));
Register(26, typeof(CurseSpell));
Register(27, typeof(FireFieldSpell));
Register(28, typeof(GreaterHealSpell));
Register(29, typeof(LightningSpell));
Register(30, typeof(ManaDrainSpell));
Register(31, typeof(RecallSpell));
// Fifth circle
Register(32, typeof(BladeSpiritsSpell));
Register(33, typeof(DispelFieldSpell));
Register(34, typeof(IncognitoSpell));
Register(35, typeof(MagicReflectSpell));
Register(36, typeof(MindBlastSpell));
Register(37, typeof(ParalyzeSpell));
Register(38, typeof(PoisonFieldSpell));
Register(39, typeof(SummonCreatureSpell));
// Sixth circle
Register(40, typeof(DispelSpell));
Register(41, typeof(EnergyBoltSpell));
Register(42, typeof(ExplosionSpell));
Register(43, typeof(InvisibilitySpell));
Register(44, typeof(MarkSpell));
Register(45, typeof(MassCurseSpell));
Register(46, typeof(ParalyzeFieldSpell));
Register(47, typeof(RevealSpell));
// Seventh circle
Register(48, typeof(ChainLightningSpell));
Register(49, typeof(EnergyFieldSpell));
Register(50, typeof(FlameStrikeSpell));
Register(51, typeof(GateTravelSpell));
Register(52, typeof(ManaVampireSpell));
Register(53, typeof(MassDispelSpell));
Register(54, typeof(MeteorSwarmSpell));
Register(55, typeof(PolymorphSpell));
// Eighth circle
Register(56, typeof(EarthquakeSpell));
Register(57, typeof(EnergyVortexSpell));
Register(58, typeof(ResurrectionSpell));
Register(59, typeof(AirElementalSpell));
Register(60, typeof(SummonDaemonSpell));
Register(61, typeof(EarthElementalSpell));
Register(62, typeof(FireElementalSpell));
Register(63, typeof(WaterElementalSpell));
if (Core.AOS)
{
// Necromancy spells
Register(100, typeof(AnimateDeadSpell));
Register(101, typeof(BloodOathSpell));
Register(102, typeof(CorpseSkinSpell));
Register(103, typeof(CurseWeaponSpell));
Register(104, typeof(EvilOmenSpell));
Register(105, typeof(HorrificBeastSpell));
Register(106, typeof(LichFormSpell));
Register(107, typeof(MindRotSpell));
Register(108, typeof(PainSpikeSpell));
Register(109, typeof(PoisonStrikeSpell));
Register(110, typeof(StrangleSpell));
Register(111, typeof(SummonFamiliarSpell));
Register(112, typeof(VampiricEmbraceSpell));
Register(113, typeof(VengefulSpiritSpell));
Register(114, typeof(WitherSpell));
Register(115, typeof(WraithFormSpell));
if (Core.SE)
Register(116, typeof(ExorcismSpell));
// Paladin abilities
Register(200, typeof(CleanseByFireSpell));
Register(201, typeof(CloseWoundsSpell));
Register(202, typeof(ConsecrateWeaponSpell));
Register(203, typeof(DispelEvilSpell));
Register(204, typeof(DivineFurySpell));
Register(205, typeof(EnemyOfOneSpell));
Register(206, typeof(HolyLightSpell));
Register(207, typeof(NobleSacrificeSpell));
Register(208, typeof(RemoveCurseSpell));
Register(209, typeof(SacredJourneySpell));
if (Core.SE)
{
// Samurai abilities
Register(400, typeof(HonorableExecution));
Register(401, typeof(Confidence));
Register(402, typeof(Evasion));
Register(403, typeof(CounterAttack));
Register(404, typeof(LightningStrike));
Register(405, typeof(MomentumStrike));
// Ninja abilities
Register(500, typeof(FocusAttack));
Register(501, typeof(DeathStrike));
Register(502, typeof(AnimalForm));
Register(503, typeof(KiAttack));
Register(504, typeof(SurpriseAttack));
Register(505, typeof(Backstab));
Register(506, typeof(Shadowjump));
Register(507, typeof(MirrorImage));
}
if (Core.ML)
{
Register(600, typeof(ArcaneCircleSpell));
Register(601, typeof(GiftOfRenewalSpell));
Register(602, typeof(ImmolatingWeaponSpell));
Register(603, typeof(AttuneWeaponSpell));
Register(604, typeof(ThunderstormSpell));
Register(605, typeof(NatureFurySpell));
Register(606, typeof(SummonFeySpell));
Register(607, typeof(SummonFiendSpell));
Register(608, typeof(ReaperFormSpell));
//Register( 609, typeof( Spellweaving.WildfireSpell ) );
Register(610, typeof(EssenceOfWindSpell));
//Register( 611, typeof( Spellweaving.DryadAllureSpell ) );
Register(612, typeof(EtherealVoyageSpell));
Register(613, typeof(WordOfDeathSpell));
Register(614, typeof(GiftOfLifeSpell));
//Register( 615, typeof( Spellweaving.ArcaneEmpowermentSpell ) );
}
if (Core.SA)
{
// Mysticism spells
//Register( 677, typeof( Mysticism.NetherBoltSpell ) );
//Register( 678, typeof( Mysticism.HealingStoneSpell ) );
//Register( 679, typeof( Mysticism.PurgeMagicSpell ) );
//Register( 680, typeof( Mysticism.EnchantSpell ) );
//Register( 681, typeof( Mysticism.SleepSpell ) );
Register(682, typeof(EagleStrikeSpell));
Register(683, typeof(AnimatedWeaponSpell));
Register(684, typeof(StoneFormSpell));
//Register( 685, typeof( Mysticism.SpellTriggerSpell ) );
//Register( 686, typeof( Mysticism.MassSleepSpell ) );
//Register( 687, typeof( Mysticism.CleansingWindsSpell ) );
//Register( 688, typeof( Mysticism.BombardSpell ) );
Register(689, typeof(SpellPlagueSpell));
Register(690, typeof(HailStormSpell));
Register(691, typeof(NetherCycloneSpell));
//Register( 692, typeof( Mysticism.RisingColossusSpell ) );
}
}
}
public static void Register(int spellId, Type type)
{
SpellRegistry.Register(spellId, type);
}
}
}

View file

@ -0,0 +1,66 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Mysticism
{
public class AnimatedWeaponSpell : MysticSpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Animated Weapon", "In Jux Por Ylem",
-1,
9002,
Reagent.Bone,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.Nightshade
);
public AnimatedWeaponSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 33.0;
public override int RequiredMana => 11;
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this);
}
public void Target(IPoint3D p)
{
if (Caster.Followers + 4 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return;
}
Map map = Caster.Map;
SpellHelper.GetSurfaceTop(ref p);
if (map == null || Caster.Player && !map.CanSpawnMobile(p.X, p.Y, p.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
int level = (int)((GetBaseSkill(Caster) + GetBoostSkill(Caster)) / 2.0);
TimeSpan duration = TimeSpan.FromSeconds(10 + level);
AnimatedWeapon summon = new AnimatedWeapon(Caster, level);
BaseCreature.Summon(summon, false, Caster, new Point3D(p), 0x212, duration);
summon.PlaySound(0x64A);
Effects.SendTargetParticles(summon, 0x3728, 10, 10, 0x13AA, (EffectLayer)255);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,69 @@
using System;
using Server.Targeting;
namespace Server.Spells.Mysticism
{
public class EagleStrikeSpell : MysticSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Eagle Strike", "Kal Por Xen",
-1,
9002,
Reagent.Bloodmoss,
Reagent.Bone,
Reagent.SpidersSilk,
Reagent.MandrakeRoot
);
public EagleStrikeSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.25);
public override double RequiredSkill => 20.0;
public override int RequiredMana => 9;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (CheckHSequence(m))
{
/* Conjures a magical eagle that assaults the Target with
* its talons, dealing energy damage.
*/
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect(2, Caster, ref m);
Caster.MovingParticles(m, 0x407A, 7, 0, false, true, 0, 0, 0xBBE, 0xFA6, 0xFFFF, 0);
Caster.PlaySound(0x2EE);
Timer.DelayCall(TimeSpan.FromSeconds(1.0), Damage, m);
}
FinishSequence();
}
private void Damage(Mobile to)
{
if (to == null)
return;
double damage = GetNewAosDamage(19, 1, 5, to);
SpellHelper.Damage(this, to, damage, 0, 0, 0, 0, 100);
to.PlaySound(0x64D);
}
}
}

View file

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using Server.Network;
namespace Server.Spells.Mysticism
{
public class HailStormSpell : MysticSpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Hail Storm", "Kal Des Ylem",
-1,
9002,
Reagent.DragonsBlood,
Reagent.Bloodmoss,
Reagent.BlackPearl,
Reagent.MandrakeRoot
);
public HailStormSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.25);
public override double RequiredSkill => 70.0;
public override int RequiredMana => 40;
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this);
}
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
/* Summons a storm of hailstones that strikes all Targets
* within a radius around the Target's Location, dealing
* cold damage.
*/
SpellHelper.Turn(Caster, p);
if (p is Item item)
p = item.GetWorldLocation();
List<Mobile> targets = new List<Mobile>();
Map map = Caster.Map;
bool pvp = false;
if (map != null)
{
PlayEffect(p, Caster.Map);
foreach (Mobile m in map.GetMobilesInRange(new Point3D(p), 2))
{
if (m == Caster)
continue;
if (SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) && Caster.CanSee(m))
{
if (!Caster.InLOS(m))
continue;
targets.Add(m);
if (m.Player)
pvp = true;
}
}
}
double damage = GetNewAosDamage(51, 1, 5, pvp);
foreach (Mobile m in targets)
{
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0);
}
}
FinishSequence();
}
private static void PlayEffect(IPoint3D p, Map map)
{
Effects.PlaySound(p, map, 0x64F);
PlaySingleEffect(p, map, -1, 1, -1, 1);
PlaySingleEffect(p, map, -2, 0, -3, -1);
PlaySingleEffect(p, map, -3, -1, -1, 1);
PlaySingleEffect(p, map, 1, 3, -1, 1);
PlaySingleEffect(p, map, -1, 1, 1, 3);
}
private static void PlaySingleEffect(IPoint3D p, Map map, int a, int b, int c, int d)
{
int x = p.X, y = p.Y, z = p.Z + 18;
SendEffectPacket(p, map, new Point3D(x + a, y + c, z), new Point3D(x + a, y + c, z));
SendEffectPacket(p, map, new Point3D(x + b, y + c, z), new Point3D(x + b, y + c, z));
SendEffectPacket(p, map, new Point3D(x + b, y + d, z), new Point3D(x + b, y + d, z));
SendEffectPacket(p, map, new Point3D(x + a, y + d, z), new Point3D(x + a, y + d, z));
SendEffectPacket(p, map, new Point3D(x + b, y + c, z), new Point3D(x + a, y + c, z));
SendEffectPacket(p, map, new Point3D(x + b, y + d, z), new Point3D(x + b, y + c, z));
SendEffectPacket(p, map, new Point3D(x + a, y + d, z), new Point3D(x + b, y + d, z));
SendEffectPacket(p, map, new Point3D(x + a, y + c, z), new Point3D(x + a, y + d, z));
}
private static void SendEffectPacket(IPoint3D p, Map map, Point3D orig, Point3D dest)
{
Effects.SendPacket(p, map,
new HuedEffect(EffectType.Moving, Serial.Zero, Serial.Zero, 0x36D4, orig, dest, 0, 0, false, false, 0x63,
0x4));
}
}
}

View file

@ -0,0 +1,93 @@
using System;
namespace Server.Spells.Mysticism
{
public abstract class MysticSpell : Spell
{
public MysticSpell(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.Mysticism;
/*
* As per OSI Publish 64:
* Imbuing is not the only skill associated with Mysticism now.
* Players can use EITHER their Focus skill or Imbuing skill.
* Evaluate Intelligence no longer has any effect on a Mystics spell power.
*/
public override double GetDamageSkill(Mobile m)
{
return Math.Max(m.Skills.Imbuing.Value, m.Skills.Focus.Value);
}
public override int GetDamageFixed(Mobile m)
{
return Math.Max(m.Skills.Imbuing.Fixed, m.Skills.Focus.Fixed);
}
public override void GetCastSkills(out double min, out double max)
{
// As per Mysticism page at the UO Herald Playguide
// This means that we have 25% success chance at min Required Skill
min = RequiredSkill - 12.5;
max = RequiredSkill + 37.5;
}
public override int GetMana()
{
return RequiredMana;
}
public override bool CheckCast()
{
if (!base.CheckCast())
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.ToString("F1")}\t{CastSkill.ToString()}\t "); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return false;
}
return true;
}
public override void OnBeginCast()
{
base.OnBeginCast();
SendCastEffect();
}
public virtual void SendCastEffect()
{
Caster.FixedEffect(0x37C4, 10, (int)(GetCastDelay().TotalSeconds * 28), 0x66C, 3);
}
public static double GetBaseSkill(Mobile m)
{
return m.Skills.Mysticism.Value;
}
public static double GetBoostSkill(Mobile m)
{
return Math.Max(m.Skills.Imbuing.Value, m.Skills.Focus.Value);
}
}
}

View file

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using Server.Network;
namespace Server.Spells.Mysticism
{
public class NetherCycloneSpell : MysticSpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Nether Cyclone", "Grav Hur",
-1,
9002,
Reagent.MandrakeRoot,
Reagent.Nightshade,
Reagent.SulfurousAsh,
Reagent.Bloodmoss
);
public NetherCycloneSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.5);
public override double RequiredSkill => 83.0;
public override int RequiredMana => 50;
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this);
}
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
/* Summons a gale of lethal winds that strikes all Targets within a radius around
* the Target's Location, dealing chaos damage. In addition to inflicting damage,
* each Target of the Nether Cyclone temporarily loses a percentage of mana and
* stamina. The effectiveness of the Nether Cyclone is determined by a comparison
* between the Caster's Mysticism and either Focus or Imbuing (whichever is greater)
* skills and the Resisting Spells skill of the Target.
*/
SpellHelper.Turn(Caster, p);
if (p is Item item)
p = item.GetWorldLocation();
List<Mobile> targets = new List<Mobile>();
Map map = Caster.Map;
bool pvp = false;
if (map != null)
{
PlayEffect(p, Caster.Map);
foreach (Mobile m in map.GetMobilesInRange(new Point3D(p), 2))
{
if (m == Caster)
continue;
if (SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) && Caster.CanSee(m))
{
if (!Caster.InLOS(m))
continue;
targets.Add(m);
if (m.Player)
pvp = true;
}
}
}
int damage = GetNewAosDamage(51, 1, 5, pvp);
double reduction = (GetBaseSkill(Caster) + GetBoostSkill(Caster)) / 1200.0;
foreach (Mobile m in targets)
{
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 0, 100);
double resistedReduction = reduction - m.Skills.MagicResist.Value / 800.0;
m.Stam -= (int)(m.StamMax * resistedReduction);
m.Mana -= (int)(m.ManaMax * resistedReduction);
}
}
FinishSequence();
}
private static void PlayEffect(IPoint3D p, Map map)
{
Effects.PlaySound(p, map, 0x64F);
PlaySingleEffect(p, map, -1, 1, -1, 1);
PlaySingleEffect(p, map, -2, 0, -3, -1);
PlaySingleEffect(p, map, -3, -1, -1, 1);
PlaySingleEffect(p, map, 1, 3, -1, 1);
PlaySingleEffect(p, map, -1, 1, 1, 3);
}
private static void PlaySingleEffect(IPoint3D p, Map map, int a, int b, int c, int d)
{
int x = p.X, y = p.Y, z = p.Z + 18;
SendEffectPacket(p, map, new Point3D(x + a, y + c, z), new Point3D(x + a, y + c, z));
SendEffectPacket(p, map, new Point3D(x + b, y + c, z), new Point3D(x + b, y + c, z));
SendEffectPacket(p, map, new Point3D(x + b, y + d, z), new Point3D(x + b, y + d, z));
SendEffectPacket(p, map, new Point3D(x + a, y + d, z), new Point3D(x + a, y + d, z));
SendEffectPacket(p, map, new Point3D(x + b, y + c, z), new Point3D(x + a, y + c, z));
SendEffectPacket(p, map, new Point3D(x + b, y + d, z), new Point3D(x + b, y + c, z));
SendEffectPacket(p, map, new Point3D(x + a, y + d, z), new Point3D(x + b, y + d, z));
SendEffectPacket(p, map, new Point3D(x + a, y + c, z), new Point3D(x + a, y + d, z));
}
private static void SendEffectPacket(IPoint3D p, Map map, Point3D orig, Point3D dest)
{
Effects.SendPacket(p, map,
new HuedEffect(EffectType.Moving, Serial.Zero, Serial.Zero, 0x375A, orig, dest, 0, 0, false, false, 0x49A,
0x4));
}
}
}

View file

@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using Server.Targeting;
namespace Server.Spells.Mysticism
{
public class SpellPlagueSpell : MysticSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Spell Plague", "Vas Rel Jux Ort",
-1,
9002,
Reagent.DaemonBone,
Reagent.DragonsBlood,
Reagent.Nightshade,
Reagent.SulfurousAsh
);
private static Dictionary<Mobile, SpellPlagueContext> m_Table = new Dictionary<Mobile, SpellPlagueContext>();
public SpellPlagueSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.25);
public override double RequiredSkill => 70.0;
public override int RequiredMana => 40;
public static void Initialize()
{
EventSink.PlayerDeath += OnPlayerDeath;
}
public override void OnCast()
{
Caster.Target = new InternalTarget(this);
}
public void Target(Mobile targeted)
{
if (!Caster.CanSee(targeted))
{
Caster.SendLocalizedMessage(500237); // Target can not be seen.
}
else if (CheckHSequence(targeted))
{
SpellHelper.Turn(Caster, targeted);
SpellHelper.CheckReflect(6, Caster, ref targeted);
/* The target is hit with an explosion of chaos damage and then inflicted
* with the spell plague curse. Each time the target is damaged while under
* the effect of the spell plague, they may suffer an explosion of chaos
* damage. The initial chance to trigger the explosion starts at 90% and
* reduces by 30% every time an explosion occurs. Once the target is
* afflicted by 3 explosions or 8 seconds have passed, that spell plague
* is removed from the target. Spell Plague will stack with other spell
* plagues so that they are applied one after the other.
*/
VisualEffect(targeted);
int damage = GetNewAosDamage(33, 1, 5, targeted);
SpellHelper.Damage(this, targeted, damage, 0, 0, 0, 0, 0);
SpellPlagueContext context = new SpellPlagueContext(this, targeted);
if (m_Table.TryGetValue(targeted, out SpellPlagueContext oldContext))
oldContext.SetNext(context);
else
{
m_Table[targeted] = context;
context.Start();
}
}
FinishSequence();
}
public static bool UnderEffect(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static void RemoveEffect(Mobile m)
{
if (m_Table.TryGetValue(m, out SpellPlagueContext context))
context.EndPlague(false);
}
public static void CheckPlague(Mobile m)
{
if (m_Table.TryGetValue(m, out SpellPlagueContext context))
context.OnDamage();
}
private static void OnPlayerDeath(PlayerDeathEventArgs e)
{
RemoveEffect(e.Mobile);
}
protected void VisualEffect(Mobile to)
{
to.PlaySound(0x658);
to.FixedParticles(0x3728, 1, 13, 0x26B8, 0x47E, 7, EffectLayer.Head, 0);
to.FixedParticles(0x3779, 1, 15, 0x251E, 0x43, 7, EffectLayer.Head, 0);
}
private class SpellPlagueContext
{
private int m_Explosions;
private DateTime m_LastExploded;
private SpellPlagueContext m_Next;
private SpellPlagueSpell m_Owner;
private Mobile m_Target;
private Timer m_Timer;
public SpellPlagueContext(SpellPlagueSpell owner, Mobile target)
{
m_Owner = owner;
m_Target = target;
}
public void SetNext(SpellPlagueContext context)
{
if (m_Next == null)
m_Next = context;
else
m_Next.SetNext(context);
}
public void Start()
{
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(8.0), EndPlague);
m_Timer.Start();
BuffInfo.AddBuff(m_Target,
new BuffInfo(BuffIcon.SpellPlague, 1031690, 1080167, TimeSpan.FromSeconds(8.5), m_Target));
}
public void OnDamage()
{
if (DateTime.Now > m_LastExploded + TimeSpan.FromSeconds(2.0))
{
int exploChance = 90 - m_Explosions * 30;
double resist = m_Target.Skills.MagicResist.Value;
if (resist >= 70)
exploChance -= (int)((resist - 70.0) * 3.0 / 10.0);
if (exploChance > Utility.Random(100))
{
m_Owner.VisualEffect(m_Target);
int damage = m_Owner.GetNewAosDamage(15 + m_Explosions * 3, 1, 5, m_Target);
m_Explosions++;
m_LastExploded = DateTime.Now;
SpellHelper.Damage(m_Owner, m_Target, damage, 0, 0, 0, 0, 0, 100);
if (m_Explosions >= 3)
EndPlague();
}
}
}
private void EndPlague()
{
EndPlague(true);
}
public void EndPlague(bool restart)
{
m_Timer?.Stop();
if (restart && m_Next != null)
{
m_Table[m_Target] = m_Next;
m_Next.Start();
}
else
{
m_Table.Remove(m_Target);
BuffInfo.RemoveBuff(m_Target, BuffIcon.SpellPlague);
}
}
}
private class InternalTarget : Target
{
private SpellPlagueSpell m_Owner;
public InternalTarget(SpellPlagueSpell owner)
: base(12, false, TargetFlags.Harmful)
{
m_Owner = owner;
}
protected override void OnTarget(Mobile from, object o)
{
if (o is Mobile mobile)
m_Owner.Target(mobile);
}
protected override void OnTargetFinish(Mobile from)
{
m_Owner.FinishSequence();
}
}
}
}

View file

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using Server.Factions;
using Server.Mobiles;
using Server.Spells.Fifth;
using Server.Spells.Ninjitsu;
using Server.Spells.Seventh;
namespace Server.Spells.Mysticism
{
public class StoneFormSpell : MysticSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Stone Form", "In Rel Ylem",
-1,
9002,
Reagent.Bloodmoss,
Reagent.FertileDirt,
Reagent.Garlic
);
private static Dictionary<Mobile, ResistanceMod[]> m_Table = new Dictionary<Mobile, ResistanceMod[]>();
public StoneFormSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 33.0;
public override int RequiredMana => 11;
public static void Initialize()
{
EventSink.PlayerDeath += OnPlayerDeath;
}
public static bool UnderEffect(Mobile m)
{
return m_Table.ContainsKey(m);
}
public override bool CheckCast()
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
return false;
}
if (!Caster.CanBeginAction<PolymorphSpell>())
{
Caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
return false;
}
if (AnimalForm.UnderTransformation(Caster))
{
Caster.SendLocalizedMessage(1063218); // You cannot use that ability in this form.
return false;
}
if (Caster.Flying)
{
Caster.SendLocalizedMessage(1113415); // You cannot use this ability while flying.
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (!Caster.CanBeginAction<PolymorphSpell>())
{
Caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
}
else if (!Caster.CanBeginAction<IncognitoSpell>() || Caster.IsBodyMod && !UnderEffect(Caster))
{
Caster.SendLocalizedMessage(1063218); // You cannot use that ability in this form.
}
else if (CheckSequence())
{
if (UnderEffect(Caster))
{
RemoveEffects(Caster);
Caster.PlaySound(0xFA);
Caster.Delta(MobileDelta.Resistances);
}
else
{
IMount mount = Caster.Mount;
if (mount != null)
mount.Rider = null;
Caster.BodyMod = 0x2C1;
Caster.HueMod = 0;
int offset = (int)((GetBaseSkill(Caster) + GetBoostSkill(Caster)) / 24.0);
ResistanceMod[] mods = {
new ResistanceMod(ResistanceType.Physical, offset),
new ResistanceMod(ResistanceType.Fire, offset),
new ResistanceMod(ResistanceType.Cold, offset),
new ResistanceMod(ResistanceType.Poison, offset),
new ResistanceMod(ResistanceType.Energy, offset)
};
for (int i = 0; i < mods.Length; ++i)
Caster.AddResistanceMod(mods[i]);
m_Table[Caster] = mods;
Caster.PlaySound(0x65A);
Caster.Delta(MobileDelta.Resistances);
BuffInfo.AddBuff(Caster, new BuffInfo(BuffIcon.StoneForm, 1080145, 1080146,
$"-10\t-2\t{offset}\t{GetResistCapBonus(Caster)}\t{GetDIBonus(Caster)}", false));
}
}
FinishSequence();
}
public static int GetDIBonus(Mobile m)
{
return (int)((GetBaseSkill(m) + GetBoostSkill(m)) / 12.0);
}
public static int GetResistCapBonus(Mobile m)
{
return (int)((GetBaseSkill(m) + GetBoostSkill(m)) / 48.0);
}
public static void RemoveEffects(Mobile m)
{
if (!m_Table.TryGetValue(m, out ResistanceMod[] mods))
return;
for (int i = 0; i < mods.Length; ++i)
m.RemoveResistanceMod(mods[i]);
m.BodyMod = 0;
m.HueMod = -1;
m_Table.Remove(m);
BuffInfo.RemoveBuff(m, BuffIcon.StoneForm);
}
private static void OnPlayerDeath(PlayerDeathEventArgs e)
{
RemoveEffects(e.Mobile);
}
}
}

View file

@ -0,0 +1,374 @@
using System;
using System.Collections.Generic;
using Server.Engines.Quests;
using Server.Engines.Quests.Necro;
using Server.Items;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class AnimateDeadSpell : NecromancerSpell, ISpellTargetingItem
{
private static SpellInfo m_Info = new SpellInfo(
"Animate Dead", "Uus Corp",
203,
9031,
Reagent.GraveDust,
Reagent.DaemonBlood
);
private static CreatureGroup[] m_Groups =
{
// Undead group--empty
new CreatureGroup(SlayerGroup.GetEntryByName(SlayerName.Silver).Types, new SummonEntry[0]),
// 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(new Type[0], 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 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 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 (int i = 0; i < m_Groups.Length; ++i)
{
CreatureGroup group = m_Groups[i];
Type[] types = group.m_Types;
bool contains = types.Length == 0;
for (int j = 0; !contains && j < types.Length; ++j)
contains = types[j].IsAssignableFrom(type);
if (contains)
return group;
}
return null;
}
public void Target(Item item)
{
MaabusCoffinComponent comp = item as MaabusCoffinComponent;
if (comp?.Addon is MaabusCoffin addon)
{
PlayerMobile pm = Caster as PlayerMobile;
QuestSystem 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
{
CreatureGroup 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())
{
Point3D p = c.GetWorldLocation();
Map 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 static void Unregister(Mobile master, Mobile summoned)
{
if (master == null)
return;
if (!m_Table.TryGetValue(master, out List<Mobile> 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 List<Mobile> list))
m_Table[master] = list = new List<Mobile>();
for (int i = list.Count - 1; i >= 0; --i)
{
if (i >= list.Count)
continue;
Mobile mob = list[i];
if (mob.Deleted)
list.RemoveAt(i--);
}
list.Add(summoned);
if (list.Count > 3)
Timer.DelayCall(TimeSpan.Zero, 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;
Mobile owner = corpse.Owner;
if (owner == null)
return;
double necromancy = caster.Skills.Necromancy.Value;
double spiritSpeak = caster.Skills.SpiritSpeak.Value;
int casterAbility = 0;
casterAbility += (int)(necromancy * 30);
casterAbility += (int)(spiritSpeak * 70);
casterAbility /= 10;
casterAbility *= 18;
if (casterAbility > owner.Fame)
casterAbility = owner.Fame;
if (casterAbility < 0)
casterAbility = 0;
Type toSummon = null;
SummonEntry[] entries = group.m_Entries;
for (int i = 0; toSummon == null && i < entries.Length; ++i)
{
SummonEntry entry = entries[i];
if (casterAbility < entry.m_Requirement)
continue;
Type[] animates = entry.m_ToSummon;
if (animates.Length >= 0)
toSummon = animates[Utility.Random(animates.Length)];
}
if (toSummon == null)
return;
Mobile summoned = null;
try
{
summoned = Activator.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)
{
int 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 SummonEntry[] m_Entries;
public Type[] m_Types;
public CreatureGroup(Type[] types, SummonEntry[] entries)
{
m_Types = types;
m_Entries = entries;
}
}
private class SummonEntry
{
public int m_Requirement;
public Type[] m_ToSummon;
public SummonEntry(int requirement, params Type[] toSummon)
{
m_ToSummon = toSummon;
m_Requirement = requirement;
}
}
}
}

View file

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class BloodOathSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Blood Oath", "In Jux Mani Xen",
203,
9031,
Reagent.DaemonBlood
);
private static Dictionary<Mobile, Mobile> m_OathTable = new Dictionary<Mobile, Mobile>();
private static Dictionary<Mobile, ExpireTimer> m_Table = new Dictionary<Mobile, ExpireTimer>();
public BloodOathSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 20.0;
public override int RequiredMana => 13;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
Caster.SendLocalizedMessage(1060508); // You can't curse that.
// only PlayerMobile and BaseCreature implement blood oath checking
else if (Caster == m || !(m is PlayerMobile || m is BaseCreature))
Caster.SendLocalizedMessage(1060508); // You can't curse that.
else if (m_OathTable.ContainsKey(Caster))
Caster.SendLocalizedMessage(1061607); // You are already bonded in a Blood Oath.
else if (m_OathTable.ContainsKey(m))
{
if (m.Player)
Caster.SendLocalizedMessage(1061608); // That player is already bonded in a Blood Oath.
else
Caster.SendLocalizedMessage(1061609); // That creature is already bonded in a Blood Oath.
}
else if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Temporarily creates a dark pact between the caster and the target.
* Any damage dealt by the target to the caster is increased, but the target receives the same amount of damage.
* The effect lasts for ((Spirit Speak skill level - target's Resist Magic skill level) / 80 ) + 8 seconds.
*
* NOTE: The above algorithm must be fixed point, it should be:
* ((ss-rm)/8)+8
*/
m_Table.TryGetValue(m, out ExpireTimer timer);
timer?.DoExpire();
m_OathTable[Caster] = Caster;
m_OathTable[m] = Caster;
m.Spell?.OnCasterHurt();
Caster.PlaySound(0x175);
Caster.FixedParticles(0x375A, 1, 17, 9919, 33, 7, EffectLayer.Waist);
Caster.FixedParticles(0x3728, 1, 13, 9502, 33, 7, (EffectLayer)255);
m.FixedParticles(0x375A, 1, 17, 9919, 33, 7, EffectLayer.Waist);
m.FixedParticles(0x3728, 1, 13, 9502, 33, 7, (EffectLayer)255);
TimeSpan duration = TimeSpan.FromSeconds((GetDamageSkill(Caster) - GetResistSkill(m)) / 8 + 8);
m.CheckSkill(SkillName.MagicResist, 0.0, 120.0); //Skill check for gain
timer = new ExpireTimer(Caster, m, duration);
timer.Start();
BuffInfo.AddBuff(Caster, new BuffInfo(BuffIcon.BloodOathCaster, 1075659, duration, Caster, m.Name));
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.BloodOathCurse, 1075661, duration, m, Caster.Name));
m_Table[m] = timer;
HarmfulSpell(m);
}
FinishSequence();
}
public static void RemoveCurse(Mobile m)
{
m_Table.TryGetValue(m, out ExpireTimer t);
t?.DoExpire();
}
public static Mobile GetBloodOath(Mobile m)
{
return m == null || m_OathTable.TryGetValue(m, out Mobile oath) && oath == m ? null : oath;
}
private class ExpireTimer : Timer
{
private Mobile m_Caster;
private DateTime m_End;
private Mobile m_Target;
public ExpireTimer(Mobile caster, Mobile target, TimeSpan delay) : base(TimeSpan.FromSeconds(1.0),
TimeSpan.FromSeconds(1.0))
{
m_Caster = caster;
m_Target = target;
m_End = DateTime.UtcNow + delay;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
if (m_Caster.Deleted || m_Target.Deleted || !m_Caster.Alive || !m_Target.Alive ||
DateTime.UtcNow >= m_End) DoExpire();
}
public void DoExpire()
{
if (m_OathTable.ContainsKey(m_Caster))
{
m_Caster.SendLocalizedMessage(1061620); // Your Blood Oath has been broken.
m_OathTable.Remove(m_Caster);
}
if (m_OathTable.ContainsKey(m_Target))
{
m_Target.SendLocalizedMessage(1061620); // Your Blood Oath has been broken.
m_OathTable.Remove(m_Target);
}
Stop();
BuffInfo.RemoveBuff(m_Caster, BuffIcon.BloodOathCaster);
BuffInfo.RemoveBuff(m_Target, BuffIcon.BloodOathCurse);
m_Table.Remove(m_Caster);
}
}
}
}

View file

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class CorpseSkinSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Corpse Skin", "In Agle Corp Ylem",
203,
9051,
Reagent.BatWing,
Reagent.GraveDust
);
private static Dictionary<Mobile, ExpireTimer> m_Table = new Dictionary<Mobile, ExpireTimer>();
public CorpseSkinSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 20.0;
public override int RequiredMana => 11;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Transmogrifies the flesh of the target creature or player to resemble rotted corpse flesh,
* making them more vulnerable to Fire and Poison damage,
* but increasing their resistance to Physical and Cold damage.
*
* The effect lasts for ((Spirit Speak skill level - target's Resist Magic skill level) / 25 ) + 40 seconds.
*
* NOTE: Algorithm above is fixed point, should be:
* ((ss-mr)/2.5) + 40
*
* NOTE: Resistance is not checked if targeting yourself
*/
if (m_Table.TryGetValue(m, out ExpireTimer timer))
timer.DoExpire();
else
m.SendLocalizedMessage(1061689); // Your skin turns dry and corpselike.
m.Spell?.OnCasterHurt();
m.FixedParticles(0x373A, 1, 15, 9913, 67, 7, EffectLayer.Head);
m.PlaySound(0x1BB);
double ss = GetDamageSkill(Caster);
double mr = Caster == m ? 0.0 : GetResistSkill(m);
m.CheckSkill(SkillName.MagicResist, 0.0, 120.0); //Skill check for gain
TimeSpan duration = TimeSpan.FromSeconds((ss - mr) / 2.5 + 40.0);
ResistanceMod[] mods = {
new ResistanceMod(ResistanceType.Fire, -15),
new ResistanceMod(ResistanceType.Poison, -15),
new ResistanceMod(ResistanceType.Cold, +10),
new ResistanceMod(ResistanceType.Physical, +10)
};
timer = new ExpireTimer(m, mods, duration);
timer.Start();
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.CorpseSkin, 1075663, duration, m));
m_Table[m] = timer;
for (int i = 0; i < mods.Length; ++i)
m.AddResistanceMod(mods[i]);
HarmfulSpell(m);
}
FinishSequence();
}
public static bool RemoveCurse(Mobile m)
{
if (!m_Table.TryGetValue(m, out ExpireTimer t))
return false;
m.SendLocalizedMessage(1061688); // Your skin returns to normal.
t?.DoExpire();
return true;
}
private class ExpireTimer : Timer
{
private Mobile m_Mobile;
private ResistanceMod[] m_Mods;
public ExpireTimer(Mobile m, ResistanceMod[] mods, TimeSpan delay) : base(delay)
{
m_Mobile = m;
m_Mods = mods;
}
public void DoExpire()
{
for (int i = 0; i < m_Mods.Length; ++i)
m_Mobile.RemoveResistanceMod(m_Mods[i]);
Stop();
BuffInfo.RemoveBuff(m_Mobile, BuffIcon.CorpseSkin);
m_Table.Remove(m_Mobile);
}
protected override void OnTick()
{
m_Mobile.SendLocalizedMessage(1061688); // Your skin returns to normal.
DoExpire();
}
}
}
}

View file

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using Server.Items;
namespace Server.Spells.Necromancy
{
public class CurseWeaponSpell : NecromancerSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Curse Weapon", "An Sanct Gra Char",
203,
9031,
Reagent.PigIron
);
private static Dictionary<BaseWeapon, ExpireTimer> m_Table = new Dictionary<BaseWeapon, ExpireTimer>();
public CurseWeaponSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.75);
public override double RequiredSkill => 0.0;
public override int RequiredMana => 7;
public override void OnCast()
{
if (!(Caster.Weapon is BaseWeapon weapon) || weapon is Fists)
{
Caster.SendLocalizedMessage(501078); // You must be holding a weapon.
}
else if (CheckSequence())
{
/* Temporarily imbues a weapon with a life draining effect.
* Half the damage that the weapon inflicts is added to the necromancer's health.
* The effects lasts for (Spirit Speak skill level / 34) + 1 seconds.
*
* NOTE: Above algorithm is fixed point, should be :
* (Spirit Speak skill level / 3.4) + 1
*
* TODO: What happens if you curse a weapon then give it to someone else? Should they get the drain effect?
*/
Caster.PlaySound(0x387);
Caster.FixedParticles(0x3779, 1, 15, 9905, 32, 2, EffectLayer.Head);
Caster.FixedParticles(0x37B9, 1, 14, 9502, 32, 5, (EffectLayer)255);
new SoundEffectTimer(Caster).Start();
TimeSpan duration = TimeSpan.FromSeconds(Caster.Skills.SpiritSpeak.Value / 3.4 + 1.0);
m_Table.TryGetValue(weapon, out ExpireTimer timer);
timer?.Stop();
weapon.Cursed = true;
m_Table[weapon] = timer = new ExpireTimer(weapon, duration);
timer.Start();
}
FinishSequence();
}
private class ExpireTimer : Timer
{
private BaseWeapon m_Weapon;
public ExpireTimer(BaseWeapon weapon, TimeSpan delay) : base(delay)
{
m_Weapon = weapon;
Priority = TimerPriority.OneSecond;
}
protected override void OnTick()
{
m_Weapon.Cursed = false;
Effects.PlaySound(m_Weapon.GetWorldLocation(), m_Weapon.Map, 0xFA);
m_Table.Remove(m_Weapon);
}
}
private class SoundEffectTimer : Timer
{
private Mobile m_Mobile;
public SoundEffectTimer(Mobile m) : base(TimeSpan.FromSeconds(0.75))
{
m_Mobile = m;
Priority = TimerPriority.FiftyMS;
}
protected override void OnTick()
{
m_Mobile.PlaySound(0xFA);
}
}
}
}

View file

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class EvilOmenSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Evil Omen", "Pas Tym An Sanct",
203,
9031,
Reagent.BatWing,
Reagent.NoxCrystal
);
private static Dictionary<Mobile, DefaultSkillMod> m_Table = new Dictionary<Mobile, DefaultSkillMod>();
public EvilOmenSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(0.75);
public override double RequiredSkill => 20.0;
public override int RequiredMana => 11;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (!(m is BaseCreature || m is PlayerMobile))
Caster.SendLocalizedMessage(1060508); // You can't curse that.
else if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Curses the target so that the next harmful event that affects them is magnified.
* Damage to the target's hit points is increased 25%,
* the poison level of the attack will be 1 higher
* and the Resist Magic skill of the target will be fixed on 50.
*
* The effect lasts for one harmful event only.
*/
m.Spell?.OnCasterHurt();
m.PlaySound(0xFC);
m.FixedParticles(0x3728, 1, 13, 9912, 1150, 7, EffectLayer.Head);
m.FixedParticles(0x3779, 1, 15, 9502, 67, 7, EffectLayer.Head);
if (!m_Table.ContainsKey(m))
{
DefaultSkillMod mod = new DefaultSkillMod(SkillName.MagicResist, false, 50.0);
if (m.Skills.MagicResist.Base > 50.0)
m.AddSkillMod(mod);
m_Table[m] = mod;
}
TimeSpan duration = TimeSpan.FromSeconds(Caster.Skills.SpiritSpeak.Value / 12 + 1.0);
Timer.DelayCall(duration, mob => TryEndEffect(mob), m);
HarmfulSpell(m);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.EvilOmen, 1075647, 1075648, duration, m));
}
FinishSequence();
}
public static bool TryEndEffect(Mobile m)
{
if (!m_Table.TryGetValue(m, out DefaultSkillMod mod))
return false;
m_Table.Remove(m);
mod?.Remove();
return true;
}
}
}

View file

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Engines.CannedEvil;
using Server.Engines.PartySystem;
using Server.Factions;
using Server.Guilds;
using Server.Items;
using Server.Regions;
namespace Server.Spells.Necromancy
{
public class ExorcismSpell : NecromancerSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Exorcism", "Ort Corp Grav",
203,
9031,
Reagent.NoxCrystal,
Reagent.GraveDust
);
private static readonly int Range = Core.ML ? 48 : 18;
private static readonly Point3D[] m_BritanniaLocs =
{
new Point3D(1470, 843, 0),
new Point3D(1857, 865, -1),
new Point3D(4220, 563, 36),
new Point3D(1732, 3528, 0),
new Point3D(1300, 644, 8),
new Point3D(3355, 302, 9),
new Point3D(1606, 2490, 5),
new Point3D(2500, 3931, 3),
new Point3D(4264, 3707, 0)
};
private static readonly Point3D[] m_IllshLocs =
{
new Point3D(1222, 474, -17),
new Point3D(718, 1360, -60),
new Point3D(297, 1014, -19),
new Point3D(986, 1006, -36),
new Point3D(1180, 1288, -30),
new Point3D(1538, 1341, -3),
new Point3D(528, 223, -38)
};
private static readonly Point3D[] m_MalasLocs =
{
new Point3D(976, 517, -30)
};
private static readonly Point3D[] m_TokunoLocs =
{
new Point3D(710, 1162, 25),
new Point3D(1034, 515, 18),
new Point3D(295, 712, 55)
};
public ExorcismSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 80.0;
public override int RequiredMana => 40;
public override bool DelayedDamage => false;
public override bool CheckCast()
{
if (Caster.Skills.SpiritSpeak.Value < 100.0)
{
Caster.SendLocalizedMessage(1072112); // You must have GM Spirit Speak to use this spell
return false;
}
return base.CheckCast();
}
public override int ComputeKarmaAward()
{
return 0; //no karma lost from this spell!
}
public override void OnCast()
{
ChampionSpawnRegion r = Caster.Region.GetRegion<ChampionSpawnRegion>();
if (r == null || !Caster.InRange(r.ChampionSpawn, Range))
{
Caster.SendLocalizedMessage(1072111); // You are not in a valid exorcism region.
}
else if (CheckSequence())
{
Map map = Caster.Map;
if (map != null)
{
IEnumerable<Mobile> targets = r.ChampionSpawn.GetMobilesInRange(Range).Where(IsValidTarget);
foreach (Mobile m in targets)
{
//Surprisingly, no sparkle type effects
m.Location = GetNearestShrine(m);
}
}
}
FinishSequence();
}
private bool IsValidTarget(Mobile m)
{
if (!m.Player || m.Alive)
return false;
Corpse c = m.Corpse as Corpse;
Map map = m.Map;
if (c?.Deleted == false && map != null && c.Map == map)
{
if (SpellHelper.IsAnyT2A(map, c.Location) && SpellHelper.IsAnyT2A(map, m.Location))
return false; //Same Map, both in T2A, ie, same 'sub server'.
if (m.Region.IsPartOf<DungeonRegion>() == Region.Find(c.Location, map).IsPartOf<DungeonRegion>())
return false; //Same Map, both in Dungeon region OR They're both NOT in a dungeon region.
//Just an approximation cause RunUO doesn't divide up the world the same way OSI does ;p
}
if (Party.Get(m)?.Contains(Caster) == true)
return false;
if (m.Guild != null && Caster.Guild != null)
{
Guild mGuild = m.Guild as Guild;
Guild cGuild = Caster.Guild as Guild;
if (mGuild.IsAlly(cGuild))
return false;
if (mGuild == cGuild)
return false;
}
Faction f = Faction.Find(m);
return Faction.Facet != m.Map || f == null || f != Faction.Find(Caster);
}
private static Point3D GetNearestShrine(Mobile m)
{
Map map = m.Map;
Point3D[] locList;
if (map == Map.Felucca || map == Map.Trammel)
locList = m_BritanniaLocs;
else if (map == Map.Ilshenar)
locList = m_IllshLocs;
else if (map == Map.Tokuno)
locList = m_TokunoLocs;
else if (map == Map.Malas)
locList = m_MalasLocs;
else
locList = new Point3D[0];
Point3D closest = Point3D.Zero;
double minDist = double.MaxValue;
for (int i = 0; i < locList.Length; i++)
{
Point3D p = locList[i];
double dist = m.GetDistanceToSqrt(p);
if (minDist > dist)
{
closest = p;
minDist = dist;
}
}
return closest;
}
}
}

View file

@ -0,0 +1,40 @@
using System;
namespace Server.Spells.Necromancy
{
public class HorrificBeastSpell : TransformationSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Horrific Beast", "Rel Xen Vas Bal",
203,
9031,
Reagent.BatWing,
Reagent.DaemonBlood
);
public HorrificBeastSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 40.0;
public override int RequiredMana => 11;
public override int Body => 746;
public override void DoEffect(Mobile m)
{
m.PlaySound(0x165);
m.FixedParticles(0x3728, 1, 13, 9918, 92, 3, EffectLayer.Head);
m.Delta(MobileDelta.WeaponDamage);
m.CheckStatTimers();
}
public override void RemoveEffect(Mobile m)
{
m.Delta(MobileDelta.WeaponDamage);
}
}
}

View file

@ -0,0 +1,44 @@
using System;
namespace Server.Spells.Necromancy
{
public class LichFormSpell : TransformationSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Lich Form", "Rel Xen Corp Ort",
203,
9031,
Reagent.GraveDust,
Reagent.DaemonBlood,
Reagent.NoxCrystal
);
public LichFormSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 70.0;
public override int RequiredMana => 23;
public override int Body => 749;
public override int FireResistOffset => -10;
public override int ColdResistOffset => +10;
public override int PoisResistOffset => +10;
public override double TickRate => 2.5;
public override void DoEffect(Mobile m)
{
m.PlaySound(0x19C);
m.FixedParticles(0x3709, 1, 30, 9904, 1108, 6, EffectLayer.RightFoot);
}
public override void OnTick(Mobile m)
{
--m.Hits;
}
}
}

View file

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class MindRotSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Mind Rot", "Wis An Ben",
203,
9031,
Reagent.BatWing,
Reagent.PigIron,
Reagent.DaemonBlood
);
private static Dictionary<Mobile, MRBucket> m_Table = new Dictionary<Mobile, MRBucket>();
public MindRotSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 30.0;
public override int RequiredMana => 17;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
Caster.SendLocalizedMessage(1060508); // You can't curse that.
else if (HasMindRotScalar(m))
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
else if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Attempts to place a curse on the Target that increases the mana cost of any spells they cast,
* for a duration based off a comparison between the Caster's Spirit Speak skill and the Target's Resisting Spells skill.
* The effect lasts for ((Spirit Speak skill level - target's Resist Magic skill level) / 50 ) + 20 seconds.
*/
m.Spell?.OnCasterHurt();
m.PlaySound(0x1FB);
m.PlaySound(0x258);
m.FixedParticles(0x373A, 1, 17, 9903, 15, 4, EffectLayer.Head);
TimeSpan duration =
TimeSpan.FromSeconds(
((GetDamageSkill(Caster) - GetResistSkill(m)) / 5.0 + 20.0) * (m.Player ? 1.0 : 2.0));
m.CheckSkill(SkillName.MagicResist, 0.0, 120.0); //Skill check for gain
SetMindRotScalar(Caster, m, m.Player ? 1.25 : 2.00, duration);
HarmfulSpell(m);
}
FinishSequence();
}
public static void ClearMindRotScalar(Mobile m)
{
if (!m_Table.TryGetValue(m, out MRBucket tmpB))
return;
BuffInfo.RemoveBuff(m, BuffIcon.Mindrot);
tmpB.m_MRExpireTimer.Stop();
m_Table.Remove(m);
m.SendLocalizedMessage(1060872); // Your mind feels normal again.
}
public static bool HasMindRotScalar(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static bool GetMindRotScalar(Mobile m, ref double scalar)
{
if (m_Table.TryGetValue(m, out MRBucket tmpB))
{
scalar = tmpB.m_Scalar;
return true;
}
return false;
}
public static void SetMindRotScalar(Mobile caster, Mobile target, double scalar, TimeSpan duration)
{
if (!m_Table.ContainsKey(target))
{
MRBucket tmpB = new MRBucket(scalar, new MRExpireTimer(caster, target, duration));
m_Table.Add(target, tmpB);
BuffInfo.AddBuff(target, new BuffInfo(BuffIcon.Mindrot, 1075665, duration, target));
tmpB.m_MRExpireTimer.Start();
target.SendLocalizedMessage(1074384);
}
}
}
public class MRExpireTimer : Timer
{
private DateTime m_End;
private Mobile m_Target;
public MRExpireTimer(Mobile caster, Mobile target, TimeSpan delay) : base(TimeSpan.FromSeconds(1.0),
TimeSpan.FromSeconds(1.0))
{
m_Target = target;
m_End = DateTime.UtcNow + delay;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
if (m_Target.Deleted || !m_Target.Alive || DateTime.UtcNow >= m_End)
{
MindRotSpell.ClearMindRotScalar(m_Target);
Stop();
}
}
}
public class MRBucket
{
public MRExpireTimer m_MRExpireTimer;
public double m_Scalar;
public MRBucket(double theScalar, MRExpireTimer theTimer)
{
m_Scalar = theScalar;
m_MRExpireTimer = theTimer;
}
}
}

View file

@ -0,0 +1,61 @@
using Server.Items;
namespace Server.Spells.Necromancy
{
public abstract class NecromancerSpell : Spell
{
public NecromancerSpell(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.Necromancy;
public override SkillName DamageSkill => SkillName.SpiritSpeak;
//public override int CastDelayBase => base.CastDelayBase; // Reference, 3
public override bool ClearHandsOnCast => false;
public override double CastDelayFastScalar =>
Core.SE
? base.CastDelayFastScalar
: 0; // Necromancer spells are not affected by fast cast items, though they are by fast cast recovery
public override int ComputeKarmaAward()
{
//TODO: Verify this formula being that Necro spells don't HAVE a circle.
//int karma = -(70 + (10 * (int)Circle));
int karma = -(40 + (int)(10 * (CastDelayBase.TotalSeconds / CastDelaySecondsPerTick)));
if (Core.ML
) // Pub 36: "Added a new property called Increased Karma Loss which grants higher karma loss for casting necromancy spells."
karma += AOS.Scale(karma, AosAttributes.GetValue(Caster, AosAttribute.IncreasedKarmaLoss));
return karma;
}
public override void GetCastSkills(out double min, out double max)
{
min = RequiredSkill;
max = Scroll != null ? min : RequiredSkill + 40.0;
}
public override bool ConsumeReagents()
{
if (base.ConsumeReagents())
return true;
if (ArcaneGem.ConsumeCharges(Caster, 1))
return true;
return false;
}
public override int GetMana()
{
return RequiredMana;
}
}
}

View file

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using Server.Misc;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class PainSpikeSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Pain Spike", "In Sar",
203,
9031,
Reagent.GraveDust,
Reagent.PigIron
);
private static Dictionary<Mobile, InternalTimer> m_Table = new Dictionary<Mobile, InternalTimer>();
public PainSpikeSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 20.0;
public override int RequiredMana => 5;
public override bool DelayedDamage => false;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
//SpellHelper.CheckReflect( (int)this.Circle, Caster, ref m ); //Irrelevent after AoS
/* Temporarily causes intense physical pain to the target, dealing direct damage.
* After 10 seconds the spell wears off, and if the target is still alive,
* some of the Hit Points lost through Pain Spike are restored.
*/
m.FixedParticles(0x37C4, 1, 8, 9916, 39, 3, EffectLayer.Head);
m.FixedParticles(0x37C4, 1, 8, 9502, 39, 4, EffectLayer.Head);
m.PlaySound(0x210);
double damage = (GetDamageSkill(Caster) - GetResistSkill(m)) / 10 + (m.Player ? 18 : 30);
m.CheckSkill(SkillName.MagicResist, 0.0, 120.0); //Skill check for gain
if (damage < 1)
damage = 1;
TimeSpan buffTime = TimeSpan.FromSeconds(10.0);
if (!m_Table.TryGetValue(m, out InternalTimer timer))
{
m_Table[m] = timer = new InternalTimer(m, damage);
timer.Start();
}
else
{
damage = Utility.RandomMinMax(3, 7);
timer.Delay += TimeSpan.FromSeconds(2.0);
buffTime = timer.Next - DateTime.UtcNow;
}
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.PainSpike, 1075667, buffTime, m, Convert.ToString((int)damage)));
// TODO: Find a better way to do this
WeightOverloading.DFA = DFAlgorithm.PainSpike;
m.Damage((int)damage, Caster);
SpellHelper.DoLeech((int)damage, Caster, m);
WeightOverloading.DFA = DFAlgorithm.Standard;
//SpellHelper.Damage( this, m, damage, 100, 0, 0, 0, 0, Misc.DFAlgorithm.PainSpike );
HarmfulSpell(m);
}
FinishSequence();
}
private class InternalTimer : Timer
{
private Mobile m_Mobile;
private int m_ToRestore;
public InternalTimer(Mobile m, double toRestore) : base(TimeSpan.FromSeconds(10.0))
{
Priority = TimerPriority.OneSecond;
m_Mobile = m;
m_ToRestore = (int)toRestore;
}
protected override void OnTick()
{
m_Table.Remove(m_Mobile);
if (m_Mobile.Alive && !m_Mobile.IsDeadBondedPet)
m_Mobile.Hits += m_ToRestore;
BuffInfo.RemoveBuff(m_Mobile, BuffIcon.PainSpike);
}
}
}
}

View file

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Items;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class PoisonStrikeSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Poison Strike", "In Vas Nox",
203,
9031,
Reagent.NoxCrystal
);
public PoisonStrikeSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(Core.ML ? 1.75 : 1.5);
public override double RequiredSkill => 50.0;
public override int RequiredMana => 17;
public override bool DelayedDamage => false;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Creates a blast of poisonous energy centered on the target.
* The main target is inflicted with a large amount of Poison damage, and all valid targets in a radius of 2 tiles around the main target are inflicted with a lesser effect.
* One tile from main target receives 50% damage, two tiles from target receives 33% damage.
*/
//CheckResisted( m ); // Check magic resist for skill, but do not use return value //reports from OSI: Necro spells don't give Resist gain
Effects.SendLocationParticles(EffectItem.Create(m.Location, m.Map, EffectItem.DefaultDuration), 0x36B0, 1,
14, 63, 7, 9915, 0);
Effects.PlaySound(m.Location, m.Map, 0x229);
double damage = Utility.RandomMinMax(Core.ML ? 32 : 36, 40) * ((300 + GetDamageSkill(Caster) * 9) / 1000);
double sdiBonus = (double)AosAttributes.GetValue(Caster, AosAttribute.SpellDamage) / 100;
double pvmDamage = damage * (1 + sdiBonus);
if (Core.ML && sdiBonus > 0.15)
sdiBonus = 0.15;
double pvpDamage = damage * (1 + sdiBonus);
Map map = m.Map;
if (map != null)
{
List<Mobile> targets = new List<Mobile>();
if (Caster.CanBeHarmful(m, false))
targets.Add(m);
targets.AddRange(m.GetMobilesInRange(2)
.Where(targ => !(Caster is BaseCreature && targ is BaseCreature && targ != Caster && m != targ && SpellHelper.ValidIndirectTarget(Caster, targ) && Caster.CanBeHarmful(targ, false))));
for (int i = 0; i < targets.Count; ++i)
{
Mobile targ = targets[i];
int num;
if (targ.InRange(m.Location, 0))
num = 1;
else if (targ.InRange(m.Location, 1))
num = 2;
else
num = 3;
Caster.DoHarmful(targ);
SpellHelper.Damage(this, targ, (m.Player && Caster.Player ? pvpDamage : pvmDamage) / num, 0, 0, 0,
100, 0);
}
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class StrangleSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Strangle", "In Bal Nox",
209,
9031,
Reagent.DaemonBlood,
Reagent.NoxCrystal
);
private static Dictionary<Mobile, InternalTimer> m_Table = new Dictionary<Mobile, InternalTimer>();
public StrangleSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 65.0;
public override int RequiredMana => 29;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
//SpellHelper.CheckReflect( (int)this.Circle, Caster, ref m ); //Irrelevent after AoS
/* Temporarily chokes off the air suply of the target with poisonous fumes.
* The target is inflicted with poison damage over time.
* The amount of damage dealt each "hit" is based off of the caster's Spirit Speak skill and the Target's current Stamina.
* The less Stamina the target has, the more damage is done by Strangle.
* Duration of the effect is Spirit Speak skill level / 10 rounds, with a minimum number of 4 rounds.
* The first round of damage is dealt after 5 seconds, and every next round after that comes 1 second sooner than the one before, until there is only 1 second between rounds.
* The base damage of the effect lies between (Spirit Speak skill level / 10) - 2 and (Spirit Speak skill level / 10) + 1.
* Base damage is multiplied by the following formula: (3 - (target's current Stamina / target's maximum Stamina) * 2).
* Example:
* For a target at full Stamina the damage multiplier is 1,
* for a target at 50% Stamina the damage multiplier is 2 and
* for a target at 20% Stamina the damage multiplier is 2.6
*/
m.Spell?.OnCasterHurt();
m.PlaySound(0x22F);
m.FixedParticles(0x36CB, 1, 9, 9911, 67, 5, EffectLayer.Head);
m.FixedParticles(0x374A, 1, 17, 9502, 1108, 4, (EffectLayer)255);
if (!m_Table.TryGetValue(m, out InternalTimer timer))
{
m_Table[m] = timer = new InternalTimer(m, Caster);
timer.Start();
}
HarmfulSpell(m);
}
//Calculations for the buff bar
double spiritlevel = Caster.Skills.SpiritSpeak.Value / 10;
if (spiritlevel < 4)
spiritlevel = 4;
int d_MinDamage = 4;
int d_MaxDamage = ((int)spiritlevel + 1) * 3;
string args = $"{d_MinDamage}\t{d_MaxDamage}";
int i_Count = (int)spiritlevel;
int i_MaxCount = i_Count;
int i_HitDelay = 5;
int i_Length = i_HitDelay;
while (i_Count > 1)
{
--i_Count;
if (i_HitDelay > 1)
{
if (i_MaxCount < 5)
{
--i_HitDelay;
}
else
{
int delay = (int)Math.Ceiling((1.0 + 5 * i_Count) / i_MaxCount);
i_HitDelay = delay <= 5 ? delay : 5;
}
}
i_Length += i_HitDelay;
}
TimeSpan t_Duration = TimeSpan.FromSeconds(i_Length);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.Strangle, 1075794, 1075795, t_Duration, m, args));
FinishSequence();
}
public static bool RemoveCurse(Mobile m)
{
if (!m_Table.TryGetValue(m, out InternalTimer timer))
return false;
timer.Stop();
m.SendLocalizedMessage(1061687); // You can breath normally again.
m_Table.Remove(m);
return true;
}
private class InternalTimer : Timer
{
private int m_Count, m_MaxCount;
private int m_HitDelay;
private double m_MinBaseDamage, m_MaxBaseDamage;
private DateTime m_NextHit;
private Mobile m_Target, m_From;
public InternalTimer(Mobile target, Mobile from) : base(TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(0.1))
{
Priority = TimerPriority.FiftyMS;
m_Target = target;
m_From = from;
double spiritLevel = from.Skills.SpiritSpeak.Value / 10;
m_MinBaseDamage = spiritLevel - 2;
m_MaxBaseDamage = spiritLevel + 1;
m_HitDelay = 5;
m_NextHit = DateTime.UtcNow + TimeSpan.FromSeconds(m_HitDelay);
m_Count = (int)spiritLevel;
if (m_Count < 4)
m_Count = 4;
m_MaxCount = m_Count;
}
protected override void OnTick()
{
if (!m_Target.Alive)
{
m_Table.Remove(m_Target);
Stop();
}
if (!m_Target.Alive || DateTime.UtcNow < m_NextHit)
return;
--m_Count;
if (m_HitDelay > 1)
{
if (m_MaxCount < 5)
{
--m_HitDelay;
}
else
{
int delay = (int)Math.Ceiling((1.0 + 5 * m_Count) / m_MaxCount);
if (delay <= 5)
m_HitDelay = delay;
else
m_HitDelay = 5;
}
}
if (m_Count == 0)
{
m_Target.SendLocalizedMessage(1061687); // You can breath normally again.
m_Table.Remove(m_Target);
Stop();
}
else
{
m_NextHit = DateTime.UtcNow + TimeSpan.FromSeconds(m_HitDelay);
double damage = m_MinBaseDamage + Utility.RandomDouble() * (m_MaxBaseDamage - m_MinBaseDamage);
damage *= 3 - (double)m_Target.Stam / m_Target.StamMax * 2;
if (damage < 1)
damage = 1;
if (!m_Target.Player)
damage *= 1.75;
AOS.Damage(m_Target, m_From, (int)damage, 0, 0, 0, 100, 0);
if (0.60 <= Utility.RandomDouble()
) // OSI: randomly revealed between first and third damage tick, guessing 60% chance
m_Target.RevealingAction();
}
}
}
}
}

View file

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Spells.Necromancy
{
public class SummonFamiliarSpell : NecromancerSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Summon Familiar", "Kal Xen Bal",
203,
9031,
Reagent.BatWing,
Reagent.GraveDust,
Reagent.DaemonBlood
);
public SummonFamiliarSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 30.0;
public override int RequiredMana => 17;
public static Dictionary<Mobile, BaseCreature> Table{ get; } = new Dictionary<Mobile, BaseCreature>();
public static SummonFamiliarEntry[] Entries{ get; } =
{
new SummonFamiliarEntry(typeof(HordeMinionFamiliar), 1060146, 30.0, 30.0), // Horde Minion
new SummonFamiliarEntry(typeof(ShadowWispFamiliar), 1060142, 50.0, 50.0), // Shadow Wisp
new SummonFamiliarEntry(typeof(DarkWolfFamiliar), 1060143, 60.0, 60.0), // Dark Wolf
new SummonFamiliarEntry(typeof(DeathAdder), 1060145, 80.0, 80.0), // Death Adder
new SummonFamiliarEntry(typeof(VampireBatFamiliar), 1060144, 100.0, 100.0) // Vampire Bat
};
public override bool CheckCast()
{
if (!(Table.TryGetValue(Caster, out BaseCreature check) && check?.Deleted == false))
return base.CheckCast();
Caster.SendLocalizedMessage(1061605); // You already have a familiar.
return false;
}
public override void OnCast()
{
if (CheckSequence())
{
Caster.CloseGump<SummonFamiliarGump>();
Caster.SendGump(new SummonFamiliarGump(Caster, Entries, this));
}
FinishSequence();
}
}
public class SummonFamiliarEntry
{
public SummonFamiliarEntry(Type type, object name, double reqNecromancy, double reqSpiritSpeak)
{
Type = type;
Name = name;
ReqNecromancy = reqNecromancy;
ReqSpiritSpeak = reqSpiritSpeak;
}
public Type Type{ get; }
public object Name{ get; }
public double ReqNecromancy{ get; }
public double ReqSpiritSpeak{ get; }
}
public class SummonFamiliarGump : Gump
{
private const int EnabledColor16 = 0x0F20;
private const int DisabledColor16 = 0x262A;
private const int EnabledColor32 = 0x18CD00;
private const int DisabledColor32 = 0x4A8B52;
private SummonFamiliarEntry[] m_Entries;
private Mobile m_From;
private SummonFamiliarSpell m_Spell;
public SummonFamiliarGump(Mobile from, SummonFamiliarEntry[] entries, SummonFamiliarSpell spell) : base(200, 100)
{
m_From = from;
m_Entries = entries;
m_Spell = spell;
AddPage(0);
AddBackground(10, 10, 250, 178, 9270);
AddAlphaRegion(20, 20, 230, 158);
AddImage(220, 20, 10464);
AddImage(220, 72, 10464);
AddImage(220, 124, 10464);
AddItem(188, 16, 6883);
AddItem(198, 168, 6881);
AddItem(8, 15, 6882);
AddItem(2, 168, 6880);
AddHtmlLocalized(30, 26, 200, 20, 1060147, EnabledColor16); // Chose thy familiar...
double necro = from.Skills.Necromancy.Value;
double spirit = from.Skills.SpiritSpeak.Value;
for (int i = 0; i < entries.Length; ++i)
{
object name = entries[i].Name;
bool enabled = necro >= entries[i].ReqNecromancy && spirit >= entries[i].ReqSpiritSpeak;
AddButton(27, 53 + i * 21, 9702, 9703, i + 1);
if (name is int intName)
AddHtmlLocalized(50, 51 + i * 21, 150, 20, intName, enabled ? EnabledColor16 : DisabledColor16);
else if (name is string strName)
AddHtml(50, 51 + i * 21, 150, 20,
$"<BASEFONT COLOR=#{(enabled ? EnabledColor32 : DisabledColor32):X6}>{strName}</BASEFONT>");
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
int index = info.ButtonID - 1;
if (index >= 0 && index < m_Entries.Length)
{
SummonFamiliarEntry entry = m_Entries[index];
double necro = m_From.Skills.Necromancy.Value;
double spirit = m_From.Skills.SpiritSpeak.Value;
#region Dueling
if ((m_From as PlayerMobile)?.DuelContext?.AllowSpellCast(m_From, m_Spell) == false)
{
}
#endregion
else if (SummonFamiliarSpell.Table.TryGetValue(m_From, out BaseCreature check) && check?.Deleted == false)
{
m_From.SendLocalizedMessage(1061605); // You already have a familiar.
}
else if (necro < entry.ReqNecromancy || spirit < entry.ReqSpiritSpeak)
{
// That familiar requires ~1_NECROMANCY~ Necromancy and ~2_SPIRIT~ Spirit Speak.
m_From.SendLocalizedMessage(1061606, $"{entry.ReqNecromancy:F1}\t{entry.ReqSpiritSpeak:F1}");
m_From.CloseGump<SummonFamiliarGump>();
m_From.SendGump(new SummonFamiliarGump(m_From, SummonFamiliarSpell.Entries, m_Spell));
}
else if (entry.Type == null)
{
m_From.SendMessage("That familiar has not yet been defined.");
m_From.CloseGump<SummonFamiliarGump>();
m_From.SendGump(new SummonFamiliarGump(m_From, SummonFamiliarSpell.Entries, m_Spell));
}
else
{
try
{
BaseCreature bc = (BaseCreature)Activator.CreateInstance(entry.Type);
// TODO: Is this right?
bc.Skills.MagicResist.Base = m_From.Skills.MagicResist.Base;
if (BaseCreature.Summon(bc, m_From, m_From.Location, -1, TimeSpan.FromDays(1.0)))
{
m_From.FixedParticles(0x3728, 1, 10, 9910, EffectLayer.Head);
bc.PlaySound(bc.GetIdleSound());
SummonFamiliarSpell.Table[m_From] = bc;
}
}
catch
{
// ignored
}
}
}
else
{
m_From.SendLocalizedMessage(1061825); // You decide not to summon a familiar.
}
}
}
}

View file

@ -0,0 +1,48 @@
namespace Server.Spells.Necromancy
{
public abstract class TransformationSpell : NecromancerSpell, ITransformationSpell
{
public TransformationSpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info)
{
}
public override bool BlockedByHorrificBeast => false;
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,53 @@
using System;
using Server.Items;
namespace Server.Spells.Necromancy
{
public class VampiricEmbraceSpell : TransformationSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Vampiric Embrace", "Rel Xen An Sanct",
203,
9031,
Reagent.BatWing,
Reagent.NoxCrystal,
Reagent.PigIron
);
public VampiricEmbraceSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 99.0;
public override int RequiredMana => 23;
public override int Body => Caster.Female ? 745 : 744;
public override int Hue => 0x847E;
public override int FireResistOffset => -25;
public override void GetCastSkills(out double min, out double max)
{
if (Caster.Skills[CastSkill].Value >= RequiredSkill)
{
min = 80.0;
max = 120.0;
}
else
{
base.GetCastSkills(out min, out max);
}
}
public override void DoEffect(Mobile m)
{
Effects.SendLocationParticles(EffectItem.Create(m.Location, m.Map, EffectItem.DefaultDuration), 0x373A, 1, 17,
1108, 7, 9914, 0);
Effects.SendLocationParticles(EffectItem.Create(m.Location, m.Map, EffectItem.DefaultDuration), 0x376A, 1, 22,
67, 7, 9502, 0);
Effects.PlaySound(m.Location, m.Map, 0x4B1);
}
}
}

View file

@ -0,0 +1,75 @@
using System;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Spells.Necromancy
{
public class VengefulSpiritSpell : NecromancerSpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Vengeful Spirit", "Kal Xen Bal Beh",
203,
9031,
Reagent.BatWing,
Reagent.GraveDust,
Reagent.PigIron
);
public VengefulSpiritSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 80.0;
public override int RequiredMana => 41;
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public override bool CheckCast()
{
if (!base.CheckCast())
return false;
if (Caster.Followers + 3 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1049645); // You have too many followers to summon that creature.
return false;
}
return true;
}
public void Target(Mobile m)
{
if (m == null)
return;
if (Caster == m)
Caster.SendLocalizedMessage(1061832); // You cannot exact vengeance on yourself.
else if (CheckHSequence(m))
{
SpellHelper.Turn(Caster, m);
/* Summons a Revenant which haunts the target until either the target or the Revenant is dead.
* Revenants have the ability to track down their targets wherever they may travel.
* A Revenant's strength is determined by the Necromancy and Spirit Speak skills of the Caster.
* The effect lasts for ((Spirit Speak skill level * 80) / 120) + 10 seconds.
*/
TimeSpan duration = TimeSpan.FromSeconds(GetDamageSkill(Caster) * 80 / 120 + 10);
Revenant rev = new Revenant(Caster, m, duration);
if (BaseCreature.Summon(rev, false, Caster, m.Location, 0x81,
TimeSpan.FromSeconds(duration.TotalSeconds + 2.0)))
rev.FixedParticles(0x373A, 1, 15, 9909, EffectLayer.Waist);
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using Server.Items;
using Server.Mobiles;
namespace Server.Spells.Necromancy
{
public class WitherSpell : NecromancerSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Wither", "Kal Vas An Flam",
203,
9031,
Reagent.NoxCrystal,
Reagent.GraveDust,
Reagent.PigIron
);
public WitherSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => 60.0;
public override int RequiredMana => 23;
public override bool DelayedDamage => false;
public override void OnCast()
{
if (CheckSequence())
{
/* Creates a withering frost around the Caster,
* which deals Cold Damage to all valid targets in a radius of 5 tiles.
*/
Map map = Caster.Map;
if (map != null)
{
List<Mobile> targets = new List<Mobile>();
BaseCreature cbc = Caster as BaseCreature;
bool isMonster = cbc?.Controlled == false && !cbc.Summoned;
foreach (Mobile m in Caster.GetMobilesInRange(Core.ML ? 4 : 5))
if (Caster != m && Caster.InLOS(m) && (isMonster || SpellHelper.ValidIndirectTarget(Caster, m)) &&
Caster.CanBeHarmful(m, false))
{
if (isMonster)
{
if (m is BaseCreature bc)
{
if (!bc.Controlled && !bc.Summoned && bc.Team == cbc.Team)
continue;
}
else if (!m.Player)
{
continue;
}
}
targets.Add(m);
}
Effects.PlaySound(Caster.Location, map, 0x1FB);
Effects.PlaySound(Caster.Location, map, 0x10B);
Effects.SendLocationParticles(EffectItem.Create(Caster.Location, map, EffectItem.DefaultDuration),
0x37CC, 1, 40, 97, 3, 9917, 0);
for (int i = 0; i < targets.Count; ++i)
{
Mobile m = targets[i];
Caster.DoHarmful(m);
m.FixedParticles(0x374A, 1, 15, 9502, 97, 3, (EffectLayer)255);
double damage = Utility.RandomMinMax(30, 35);
damage *= 300 + m.Karma / 100 + GetDamageSkill(Caster) * 10;
damage /= 1000;
int sdiBonus = AosAttributes.GetValue(Caster, AosAttribute.SpellDamage);
// PvP spell damage increase cap of 15% from an item<65>s magic property in Publish 33(SE)
if (Core.SE && m.Player && Caster.Player && sdiBonus > 15)
sdiBonus = 15;
damage *= 100 + sdiBonus;
damage /= 100;
// TODO: cap?
//if ( damage > 40 )
// damage = 40;
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0);
}
}
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,49 @@
using System;
using Server.Mobiles;
namespace Server.Spells.Necromancy
{
public class WraithFormSpell : TransformationSpell
{
private static SpellInfo m_Info = new SpellInfo(
"Wraith Form", "Rel Xen Um",
203,
9031,
Reagent.NoxCrystal,
Reagent.PigIron
);
public WraithFormSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(2.0);
public override double RequiredSkill => 20.0;
public override int RequiredMana => 17;
public override int Body => Caster.Female ? 747 : 748;
public override int Hue => Caster.Female ? 0 : 0x4001;
public override int PhysResistOffset => +15;
public override int FireResistOffset => -5;
public override int ColdResistOffset => 0;
public override int PoisResistOffset => 0;
public override int NrgyResistOffset => -5;
public override void DoEffect(Mobile m)
{
if (m is PlayerMobile mobile)
mobile.IgnoreMobiles = true;
m.PlaySound(0x17F);
m.FixedParticles(0x374A, 1, 15, 9902, 1108, 4, EffectLayer.Waist);
}
public override void RemoveEffect(Mobile m)
{
if (m is PlayerMobile mobile && mobile.AccessLevel == AccessLevel.Player)
mobile.IgnoreMobiles = false;
}
}
}

View file

@ -0,0 +1,616 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Spells.Fifth;
using Server.Spells.Seventh;
namespace Server.Spells.Ninjitsu
{
public class AnimalForm : NinjaSpell
{
public enum MorphResult
{
Success,
Fail,
NoSkill
}
private static SpellInfo m_Info = new SpellInfo(
"Animal Form", null,
-1,
9002
);
private static Dictionary<Mobile, int> m_LastAnimalForms = new Dictionary<Mobile, int>();
private static Dictionary<Mobile, AnimalFormContext> m_Table = new Dictionary<Mobile, AnimalFormContext>();
private bool m_WasMoving;
public AnimalForm(Mobile caster, Item scroll)
: base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 0.0;
public override int RequiredMana => Core.ML ? 10 : 0;
public override int CastRecoveryBase => Core.ML ? 10 : base.CastRecoveryBase;
public override bool BlockedByAnimalForm => false;
public static AnimalFormEntry[] Entries{ get; } =
{
new AnimalFormEntry(typeof(Kirin), 1029632, 9632, 0, 1070811, 100.0, 0x84, 0, 0),
new AnimalFormEntry(typeof(Unicorn), 1018214, 9678, 0, 1070812, 100.0, 0x7A, 0, 0),
new AnimalFormEntry(typeof(BakeKitsune), 1030083, 10083, 0, 1070810, 82.5, 0xF6, 0, 0),
new AnimalFormEntry(typeof(GreyWolf), 1028482, 9681, 2309, 1070810, 82.5, 0x19, 0x8FD, 0x90E),
new AnimalFormEntry(typeof(Llama), 1028438, 8438, 0, 1070809, 70.0, 0xDC, 0, 0),
new AnimalFormEntry(typeof(ForestOstard), 1018273, 8503, 2212, 1070809, 70.0, 0xDB, 0x899, 0x8B0),
new AnimalFormEntry(typeof(BullFrog), 1028496, 8496, 2003, 1070807, 50.0, 0x51, 0x7D1, 0x7D6, false, false),
new AnimalFormEntry(typeof(GiantSerpent), 1018114, 9663, 2009, 1070808, 50.0, 0x15, 0x7D1, 0x7E2, false, false),
new AnimalFormEntry(typeof(Dog), 1018280, 8476, 2309, 1070806, 40.0, 0xD9, 0x8FD, 0x90E, false, false),
new AnimalFormEntry(typeof(Cat), 1018264, 8475, 2309, 1070806, 40.0, 0xC9, 0x8FD, 0x90E, false, false),
new AnimalFormEntry(typeof(Rat), 1018294, 8483, 2309, 1070805, 20.0, 0xEE, 0x8FD, 0x90E, true, false),
new AnimalFormEntry(typeof(Rabbit), 1028485, 8485, 2309, 1070805, 20.0, 0xCD, 0x8FD, 0x90E, true, false),
new AnimalFormEntry(typeof(Squirrel), 1031671, 11671, 0, 0, 20.0, 0x116, 0, 0, false, false),
new AnimalFormEntry(typeof(Ferret), 1031672, 11672, 0, 1075220, 40.0, 0x117, 0, 0, false, false, true),
new AnimalFormEntry(typeof(CuSidhe), 1031670, 11670, 0, 1075221, 60.0, 0x115, 0, 0, false, false),
new AnimalFormEntry(typeof(Reptalon), 1075202, 11669, 0, 1075222, 90.0, 0x114, 0, 0, false, false)
};
public static void Initialize()
{
EventSink.Login += OnLogin;
}
public static void OnLogin(LoginEventArgs e)
{
if (GetContext(e.Mobile)?.SpeedBoost == true)
e.Mobile.Send(SpeedControl.MountSpeed);
}
public override bool CheckCast()
{
if (!Caster.CanBeginAction<PolymorphSpell>())
{
Caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
return false;
}
if (TransformationSpellHelper.UnderTransformation(Caster))
{
Caster.SendLocalizedMessage(1063219); // You cannot mimic an animal while in that form.
return false;
}
if (DisguiseTimers.IsDisguised(Caster))
{
Caster.SendLocalizedMessage(1061631); // You can't do that while disguised.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable)
{
return false;
}
private bool CasterIsMoving()
{
return Core.TickCount - Caster.LastMoveTime <= Caster.ComputeMovementSpeed(Caster.Direction);
}
public override void OnBeginCast()
{
base.OnBeginCast();
Caster.FixedEffect(0x37C4, 10, 14, 4, 3);
m_WasMoving = CasterIsMoving();
}
public override bool CheckFizzle()
{
// Spell is initially always successful, and with no skill gain.
return true;
}
public override void OnCast()
{
if (!Caster.CanBeginAction<PolymorphSpell>())
{
Caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
}
else if (TransformationSpellHelper.UnderTransformation(Caster))
{
Caster.SendLocalizedMessage(1063219); // You cannot mimic an animal while in that form.
}
else if (!Caster.CanBeginAction<IncognitoSpell>() || Caster.IsBodyMod && GetContext(Caster) == null)
{
DoFizzle();
}
else if (CheckSequence())
{
AnimalFormContext context = GetContext(Caster);
int mana = ScaleMana(RequiredMana);
if (mana > Caster.Mana)
{
Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
}
else if (context != null)
{
RemoveContext(Caster, context, true);
Caster.Mana -= mana;
}
else if (Caster is PlayerMobile)
{
bool skipGump = m_WasMoving || CasterIsMoving();
if (GetLastAnimalForm(Caster) == -1 || !skipGump)
{
Caster.CloseGump<AnimalFormGump>();
Caster.SendGump(new AnimalFormGump(Caster, Entries, this));
}
else
{
if (Morph(Caster, GetLastAnimalForm(Caster)) == MorphResult.Fail)
{
DoFizzle();
}
else
{
Caster.FixedParticles(0x3728, 10, 13, 2023, EffectLayer.Waist);
Caster.Mana -= mana;
}
}
}
else
{
if (Morph(Caster, GetLastAnimalForm(Caster)) == MorphResult.Fail)
{
DoFizzle();
}
else
{
Caster.FixedParticles(0x3728, 10, 13, 2023, EffectLayer.Waist);
Caster.Mana -= mana;
}
}
}
FinishSequence();
}
public int GetLastAnimalForm(Mobile m)
{
return m_LastAnimalForms.TryGetValue(m, out int value) ? value : -1;
}
public static MorphResult Morph(Mobile m, int entryID)
{
if (entryID < 0 || entryID >= Entries.Length)
return MorphResult.Fail;
AnimalFormEntry entry = Entries[entryID];
m_LastAnimalForms[m] = entryID; //On OSI, it's the last /attempted/ one not the last succeeded one
if (m.Skills.Ninjitsu.Value < entry.ReqSkill)
{
string args = $"{entry.ReqSkill:F1}\t{SkillName.Ninjitsu}\t ";
m.SendLocalizedMessage(1063013,
args); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return MorphResult.NoSkill;
}
/*
if ( !m.CheckSkill( SkillName.Ninjitsu, entry.ReqSkill, entry.ReqSkill + 37.5 ) )
return MorphResult.Fail;
*
* On OSI,it seems you can only gain starting at '0' using Animal form.
*/
double ninjitsu = m.Skills.Ninjitsu.Value;
if (ninjitsu < entry.ReqSkill + 37.5)
{
double chance = (ninjitsu - entry.ReqSkill) / 37.5;
if (chance < Utility.RandomDouble())
return MorphResult.Fail;
}
m.CheckSkill(SkillName.Ninjitsu, 0.0, 37.5);
if (!BaseFormTalisman.EntryEnabled(m, entry.Type))
return MorphResult.Success; // Still consumes mana, just no effect
BaseMount.Dismount(m);
int bodyMod = entry.BodyMod;
int hueMod = entry.HueMod;
m.BodyMod = bodyMod;
m.HueMod = hueMod;
if (entry.SpeedBoost)
m.Send(SpeedControl.MountSpeed);
SkillMod mod = null;
if (entry.StealthBonus)
{
mod = new DefaultSkillMod(SkillName.Stealth, true, 20.0) { ObeyCap = true };
m.AddSkillMod(mod);
}
SkillMod stealingMod = null;
if (entry.StealingBonus)
{
stealingMod = new DefaultSkillMod(SkillName.Stealing, true, 10.0) { ObeyCap = true };
m.AddSkillMod(stealingMod);
}
Timer timer = new AnimalFormTimer(m, bodyMod, hueMod);
timer.Start();
AddContext(m, new AnimalFormContext(timer, mod, entry.SpeedBoost, entry.Type, stealingMod));
m.CheckStatTimers();
return MorphResult.Success;
}
public static void AddContext(Mobile m, AnimalFormContext context)
{
m_Table[m] = context;
if (context.Type == typeof(BakeKitsune) || context.Type == typeof(GreyWolf))
m.CheckStatTimers();
}
public static void RemoveContext(Mobile m, bool resetGraphics)
{
AnimalFormContext context = GetContext(m);
if (context != null)
RemoveContext(m, context, resetGraphics);
}
public static void RemoveContext(Mobile m, AnimalFormContext context, bool resetGraphics)
{
m_Table.Remove(m);
if (context.SpeedBoost)
m.Send(SpeedControl.Disable);
SkillMod mod = context.Mod;
if (mod != null)
m.RemoveSkillMod(mod);
mod = context.StealingMod;
if (mod != null)
m.RemoveSkillMod(mod);
if (resetGraphics)
{
m.HueMod = -1;
m.BodyMod = 0;
}
m.FixedParticles(0x3728, 10, 13, 2023, EffectLayer.Waist);
context.Timer.Stop();
}
public static AnimalFormContext GetContext(Mobile m)
{
return m_Table.TryGetValue(m, out AnimalFormContext context) ? context : null;
}
public static bool UnderTransformation(Mobile m)
{
return m_Table.ContainsKey(m);
}
public static bool UnderTransformation(Mobile m, Type type)
{
return GetContext(m)?.Type == type;
}
/*
private delegate void AnimalFormCallback( Mobile from );
private delegate bool AnimalFormRequirementCallback( Mobile from );
*/
public class AnimalFormEntry
{
private int m_HueModMax;
private int m_HueModMin;
/*
private AnimalFormCallback m_TransformCallback;
private AnimalFormCallback m_UntransformCallback;
private AnimalFormRequirementCallback m_RequirementCallback;
*/
public AnimalFormEntry(Type type, TextDefinition name, int itemID, int hue, int tooltip, double reqSkill,
int bodyMod, int hueModMin, int hueModMax, bool stealthBonus = false, bool speedBoost = true, bool stealingBonus = false)
{
Type = type;
Name = name;
ItemID = itemID;
Hue = hue;
Tooltip = tooltip;
ReqSkill = reqSkill;
BodyMod = bodyMod;
m_HueModMin = hueModMin;
m_HueModMax = hueModMax;
StealthBonus = stealthBonus;
SpeedBoost = speedBoost;
StealingBonus = stealingBonus;
}
public Type Type{ get; }
public TextDefinition Name{ get; }
public int ItemID{ get; }
public int Hue{ get; }
public int Tooltip{ get; }
public double ReqSkill{ get; }
public int BodyMod{ get; }
public int HueMod => Utility.RandomMinMax(m_HueModMin, m_HueModMax);
public bool StealthBonus{ get; }
public bool SpeedBoost{ get; }
public bool StealingBonus{ get; }
}
public class AnimalFormGump : Gump
{
//TODO: Convert this for ML to the BaseImageTileButtonsGump
private Mobile m_Caster;
private AnimalForm m_Spell;
public AnimalFormGump(Mobile caster, AnimalFormEntry[] entries, AnimalForm spell)
: base(50, 50)
{
m_Caster = caster;
m_Spell = spell;
AddPage(0);
AddBackground(0, 0, 520, 404, 0x13BE);
AddImageTiled(10, 10, 500, 20, 0xA40);
AddImageTiled(10, 40, 500, 324, 0xA40);
AddImageTiled(10, 374, 500, 20, 0xA40);
AddAlphaRegion(10, 10, 500, 384);
AddHtmlLocalized(14, 12, 500, 20, 1063394, 0x7FFF); // <center>Polymorph Selection Menu</center>
AddButton(10, 374, 0xFB1, 0xFB2, 0);
AddHtmlLocalized(45, 376, 450, 20, 1011012, 0x7FFF); // CANCEL
double ninjitsu = caster.Skills.Ninjitsu.Value;
int current = 0;
for (int i = 0; i < entries.Length; ++i)
{
bool enabled = ninjitsu >= entries[i].ReqSkill && BaseFormTalisman.EntryEnabled(caster, entries[i].Type);
int page = current / 10 + 1;
int pos = current % 10;
if (pos == 0)
{
if (page > 1)
{
AddButton(400, 374, 0xFA5, 0xFA7, 0, GumpButtonType.Page, page);
AddHtmlLocalized(440, 376, 60, 20, 1043353, 0x7FFF); // Next
}
AddPage(page);
if (page > 1)
{
AddButton(300, 374, 0xFAE, 0xFB0, 0, GumpButtonType.Page, 1);
AddHtmlLocalized(340, 376, 60, 20, 1011393, 0x7FFF); // Back
}
}
if (!enabled)
continue;
int x = pos % 2 == 0 ? 14 : 264;
int y = pos / 2 * 64 + 44;
Rectangle2D b = ItemBounds.Table[entries[i].ItemID];
AddImageTiledButton(x, y, 0x918, 0x919, i + 1, GumpButtonType.Reply, 0, entries[i].ItemID,
entries[i].Hue, 40 - b.Width / 2 - b.X, 30 - b.Height / 2 - b.Y, entries[i].Tooltip);
AddHtmlLocalized(x + 84, y, 250, 60, entries[i].Name, 0x7FFF);
current++;
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
int entryID = info.ButtonID - 1;
if (entryID < 0 || entryID >= AnimalForm.Entries.Length)
return;
int mana = m_Spell.ScaleMana(m_Spell.RequiredMana);
AnimalFormEntry entry = AnimalForm.Entries[entryID];
if (mana > m_Caster.Mana)
{
m_Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
}
else if (m_Caster is PlayerMobile mobile && mobile.MountBlockReason != BlockMountType.None)
{
mobile.SendLocalizedMessage(1063108); // You cannot use this ability right now.
}
else if (BaseFormTalisman.EntryEnabled(sender.Mobile, entry.Type))
{
#region Dueling
if ((m_Caster as PlayerMobile)?.DuelContext?.AllowSpellCast(m_Caster, m_Spell) == false)
{
}
#endregion
else if (Morph(m_Caster, entryID) == MorphResult.Fail)
{
m_Caster.LocalOverheadMessage(MessageType.Regular, 0x3B2, 502632); // The spell fizzles.
m_Caster.FixedParticles(0x3735, 1, 30, 9503, EffectLayer.Waist);
m_Caster.PlaySound(0x5C);
}
else
{
m_Caster.FixedParticles(0x3728, 10, 13, 2023, EffectLayer.Waist);
m_Caster.Mana -= mana;
}
}
}
}
}
public class AnimalFormContext
{
public AnimalFormContext(Timer timer, SkillMod mod, bool speedBoost, Type type, SkillMod stealingMod)
{
Timer = timer;
Mod = mod;
SpeedBoost = speedBoost;
Type = type;
StealingMod = stealingMod;
}
public Timer Timer{ get; }
public SkillMod Mod{ get; }
public bool SpeedBoost{ get; }
public Type Type{ get; }
public SkillMod StealingMod{ get; }
}
public class AnimalFormTimer : Timer
{
private int m_Body;
private int m_Counter;
private int m_Hue;
private Mobile m_LastTarget;
private Mobile m_Mobile;
public AnimalFormTimer(Mobile from, int body, int hue)
: base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0))
{
m_Mobile = from;
m_Body = body;
m_Hue = hue;
m_Counter = 0;
Priority = TimerPriority.FiftyMS;
}
protected override void OnTick()
{
if (m_Mobile.Deleted || !m_Mobile.Alive || m_Mobile.Body != m_Body || m_Mobile.Hue != m_Hue)
{
AnimalForm.RemoveContext(m_Mobile, true);
Stop();
}
else
{
if (m_Body == 0x115) // Cu Sidhe
{
if (m_Counter++ >= 8)
{
if (m_Mobile.Hits < m_Mobile.HitsMax && m_Mobile.Backpack != null)
{
Bandage b = m_Mobile.Backpack.FindItemByType<Bandage>();
if (b != null)
{
m_Mobile.Hits += Utility.RandomMinMax(20, 50);
b.Consume();
}
}
m_Counter = 0;
}
}
else if (m_Body == 0x114) // Reptalon
{
if (m_Mobile.Combatant != null && m_Mobile.Combatant != m_LastTarget)
{
m_Counter = 1;
m_LastTarget = m_Mobile.Combatant;
}
if (m_Mobile.Warmode && m_LastTarget?.Alive == true && m_LastTarget?.Deleted != true &&
m_Counter-- <= 0)
{
if (m_Mobile.CanBeHarmful(m_LastTarget) && m_LastTarget.Map == m_Mobile.Map &&
m_LastTarget.InRange(m_Mobile.Location, BaseCreature.DefaultRangePerception) &&
m_Mobile.InLOS(m_LastTarget))
{
m_Mobile.Direction = m_Mobile.GetDirectionTo(m_LastTarget);
m_Mobile.Freeze(TimeSpan.FromSeconds(1));
m_Mobile.PlaySound(0x16A);
DelayCall(TimeSpan.FromSeconds(1.3), BreathEffect_Callback, m_LastTarget);
}
m_Counter = Math.Min((int)m_Mobile.GetDistanceToSqrt(m_LastTarget), 10);
}
}
}
}
public void BreathEffect_Callback(Mobile target)
{
if (m_Mobile.CanBeHarmful(target))
{
m_Mobile.RevealingAction();
m_Mobile.PlaySound(0x227);
Effects.SendMovingEffect(m_Mobile, target, 0x36D4, 5, 0, false, false);
DelayCall(TimeSpan.FromSeconds(1), BreathDamage_Callback, target);
}
}
public void BreathDamage_Callback(Mobile target)
{
if (m_Mobile.CanBeHarmful(target))
{
m_Mobile.RevealingAction();
m_Mobile.DoHarmful(target);
AOS.Damage(target, m_Mobile, 20, !target.Player, 0, 100, 0, 0, 0);
}
}
}
}

View file

@ -0,0 +1,71 @@
using System;
using Server.SkillHandlers;
namespace Server.Spells.Ninjitsu
{
public class Backstab : NinjaMove
{
public override int BaseMana => 30;
public override double RequiredSkill => Core.ML ? 40.0 : 20.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063089); // You prepare to Backstab your opponent.
public override bool ValidatesDuringHit => false;
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
double ninjitsu = attacker.Skills.Ninjitsu.Value;
return 1.0 + ninjitsu / 360 + Tracking.GetStalkingBonus(attacker, defender) / 100;
}
public override bool Validate(Mobile from)
{
if (!from.Hidden || from.AllowedStealthSteps <= 0)
{
from.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.Validate(from);
}
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
bool valid = Validate(attacker) && CheckMana(attacker, true);
if (valid)
{
attacker.BeginAction<Stealth>();
Timer.DelayCall(TimeSpan.FromSeconds(5.0), delegate { attacker.EndAction<Stealth>(); });
}
return valid;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
//Validates before swing
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063090); // You quickly stab your opponent as you come out of hiding!
defender.FixedParticles(0x37B9, 1, 5, 0x251D, 0x651, 0, EffectLayer.Waist);
attacker.RevealingAction();
CheckGain(attacker);
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063161); // You failed to properly use the element of surprise.
attacker.RevealingAction();
}
}
}

View file

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using Server.Items;
using Server.SkillHandlers;
namespace Server.Spells.Ninjitsu
{
public class DeathStrike : NinjaMove
{
private static Dictionary<Mobile, DeathStrikeInfo> m_Table = new Dictionary<Mobile, DeathStrikeInfo>();
public override int BaseMana => 30;
public override double RequiredSkill => 85.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063091); // You prepare to hit your opponent with a Death Strike.
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
return 0.5;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
ClearCurrentMove(attacker);
double ninjitsu = attacker.Skills.Ninjitsu.Value;
double chance;
// TODO: should be defined onHit method, what if the player hit and remove the weapon before process? ;)
bool isRanged = attacker.Weapon is BaseRanged;
if (ninjitsu < 100) //This formula is an approximation from OSI data. TODO: find correct formula
chance = 30 + (ninjitsu - 85) * 2.2;
else
chance = 63 + (ninjitsu - 100) * 1.1;
if (chance / 100 < Utility.RandomDouble())
{
attacker.SendLocalizedMessage(1070779); // You missed your opponent with a Death Strike.
return;
}
int damageBonus = 0;
if (m_Table.TryGetValue(defender, out DeathStrikeInfo info))
{
defender.SendLocalizedMessage(1063092); // Your opponent lands another Death Strike!
if (info.m_Steps > 0)
damageBonus = attacker.Skills.Ninjitsu.Fixed / 150;
info.m_Timer?.Stop();
m_Table.Remove(defender);
}
else
{
defender.SendLocalizedMessage(1063093); // You have been hit by a Death Strike! Move with caution!
}
attacker.SendLocalizedMessage(1063094); // You inflict a Death Strike upon your opponent!
defender.FixedParticles(0x374A, 1, 17, 0x26BC, EffectLayer.Waist);
attacker.PlaySound(attacker.Female ? 0x50D : 0x50E);
info = new DeathStrikeInfo(defender, attacker, damageBonus, isRanged)
{
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(5.0), ProcessDeathStrike, defender)
};
m_Table[defender] = info;
CheckGain(attacker);
}
public static void AddStep(Mobile m)
{
if (m_Table.TryGetValue(m, out DeathStrikeInfo info) && ++info.m_Steps >= 5)
ProcessDeathStrike(m);
}
private static void ProcessDeathStrike(Mobile defender)
{
if (!m_Table.TryGetValue(defender, out DeathStrikeInfo info))
return;
int damage;
double ninjitsu = info.m_Attacker.Skills.Ninjitsu.Value;
double stalkingBonus = Tracking.GetStalkingBonus(info.m_Attacker, info.m_Target);
if (Core.ML)
{
double scalar = (info.m_Attacker.Skills.Hiding.Value +
info.m_Attacker.Skills.Stealth.Value) / 220;
if (scalar > 1)
scalar = 1;
// New formula doesn't apply DamageBonus anymore, caps must be, directly, 60/30.
if (info.m_Steps >= 5)
damage = (int)Math.Floor(Math.Min(60, ninjitsu / 3 * (0.3 + 0.7 * scalar) + stalkingBonus));
else
damage = (int)Math.Floor(Math.Min(30, ninjitsu / 9 * (0.3 + 0.7 * scalar) + stalkingBonus));
if (info.m_isRanged)
damage /= 2;
}
else
{
int divisor = info.m_Steps >= 5 ? 30 : 80;
double baseDamage = ninjitsu / divisor * 10;
int maxDamage = info.m_Steps >= 5 ? 62 : 22;
damage = Math.Max(0, Math.Min(maxDamage, (int)(baseDamage + stalkingBonus))) + info.m_DamageBonus;
}
if (Core.ML)
info.m_Target.Damage(damage, info.m_Attacker); // Damage is direct.
else
AOS.Damage(info.m_Target, info.m_Attacker, damage, true, 100, 0, 0, 0, 0, 0, 0, false, false,
true); // Damage is physical.
info.m_Timer?.Stop();
m_Table.Remove(info.m_Target);
}
private class DeathStrikeInfo
{
public Mobile m_Attacker;
public int m_DamageBonus;
public bool m_isRanged;
public int m_Steps;
public Mobile m_Target;
public Timer m_Timer;
public DeathStrikeInfo(Mobile target, Mobile attacker, int damageBonus, bool isRanged)
{
m_Target = target;
m_Attacker = attacker;
m_DamageBonus = damageBonus;
m_isRanged = isRanged;
}
}
}
}

View file

@ -0,0 +1,66 @@
using Server.Items;
namespace Server.Spells.Ninjitsu
{
public class FocusAttack : NinjaMove
{
public override int BaseMana => Core.ML ? 10 : 20;
public override double RequiredSkill => Core.ML ? 30.0 : 60;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063095); // You prepare to focus all of your abilities into your next strike.
public override bool Validate(Mobile from)
{
if (from.FindItemOnLayer(Layer.TwoHanded) as BaseShield != null)
{
from.SendLocalizedMessage(1063096); // You cannot use this ability while holding a shield.
return false;
}
Item handOne = from.FindItemOnLayer(Layer.OneHanded) as BaseWeapon;
if (handOne != null && !(handOne is BaseRanged))
return base.Validate(from);
Item handTwo = from.FindItemOnLayer(Layer.TwoHanded) as BaseWeapon;
if (handTwo != null && !(handTwo is BaseRanged))
return base.Validate(from);
from.SendLocalizedMessage(1063097); // You must be wielding a melee weapon without a shield to use this ability.
return false;
}
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
double ninjitsu = attacker.Skills.Ninjitsu.Value;
return 1.0 + ninjitsu * ninjitsu / 43636;
}
public override double GetPropertyBonus(Mobile attacker)
{
double ninjitsu = attacker.Skills.Ninjitsu.Value;
double bonus = ninjitsu * ninjitsu / 43636;
return 1.0 + (bonus * 3 + 0.01);
}
public override bool OnBeforeDamage(Mobile attacker, Mobile defender)
{
return Validate(attacker) && CheckMana(attacker, true);
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063098); // You focus all of your abilities and strike with deadly force!
attacker.PlaySound(0x510);
CheckGain(attacker);
}
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using Server.Items;
namespace Server.Spells.Ninjitsu
{
public class KiAttack : NinjaMove
{
private static Dictionary<Mobile, KiAttackInfo> m_Table = new Dictionary<Mobile, KiAttackInfo>();
public override int BaseMana => 25;
public override double RequiredSkill => 80.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063099); // Your Ki Attack must be complete within 2 seconds for the damage bonus!
public override void OnUse(Mobile from)
{
if (!Validate(from))
return;
KiAttackInfo info = new KiAttackInfo(from);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(2.0), EndKiAttack, info);
m_Table[from] = info;
}
public override bool Validate(Mobile from)
{
if (from.Hidden && from.AllowedStealthSteps > 0)
{
from.SendLocalizedMessage(1063127); // You cannot use this ability while in stealth mode.
return false;
}
if (Core.ML && from.Weapon is BaseRanged)
{
from.SendLocalizedMessage(1075858); // You can only use this with melee attacks.
return false;
}
return base.Validate(from);
}
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
if (attacker.Hidden)
return 1.0;
/*
* Pub40 changed pvp damage max to 55%
*/
return 1.0 + GetBonus(attacker) / (Core.ML && attacker.Player && defender.Player ? 40 : 10);
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
if (GetBonus(attacker) == 0.0)
{
attacker.SendLocalizedMessage(1063101); // You were too close to your target to cause any additional damage.
}
else
{
attacker.FixedParticles(0x37BE, 1, 5, 0x26BD, 0x0, 0x1, EffectLayer.Waist);
attacker.PlaySound(0x510);
attacker.SendLocalizedMessage(
1063100); // Your quick flight to your target causes extra damage as you strike!
defender.FixedParticles(0x37BE, 1, 5, 0x26BD, 0, 0x1, EffectLayer.Waist);
CheckGain(attacker);
}
ClearCurrentMove(attacker);
}
public override void OnClearMove(Mobile from)
{
if (!m_Table.TryGetValue(from, out KiAttackInfo info))
return;
info.m_Timer.Stop();
m_Table.Remove(info.m_Mobile);
}
public static double GetBonus(Mobile from)
{
if (!m_Table.TryGetValue(from, out KiAttackInfo info))
return 0;
int xDelta = info.m_Location.X - from.X;
int yDelta = info.m_Location.Y - from.Y;
double bonus = Math.Sqrt(xDelta * xDelta + yDelta * yDelta);
if (bonus > 20.0)
bonus = 20.0;
return bonus;
}
private static void EndKiAttack(KiAttackInfo info)
{
info.m_Timer?.Stop();
ClearCurrentMove(info.m_Mobile);
info.m_Mobile.SendLocalizedMessage(1063102); // You failed to complete your Ki Attack in time.
m_Table.Remove(info.m_Mobile);
}
private class KiAttackInfo
{
public Point3D m_Location;
public Mobile m_Mobile;
public Timer m_Timer;
public KiAttackInfo(Mobile m)
{
m_Mobile = m;
m_Location = m.Location;
}
}
}
}

View file

@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using Server.Items;
using Server.Mobiles;
using Server.Spells;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
namespace Server.Spells.Ninjitsu
{
public class MirrorImage : NinjaSpell
{
private static Dictionary<Mobile, int> m_CloneCount = new Dictionary<Mobile, int>();
private static SpellInfo m_Info = new SpellInfo(
"Mirror Image", null,
-1,
9002
);
public MirrorImage(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => Core.ML ? 20.0 : 40.0;
public override int RequiredMana => 10;
public override bool BlockedByAnimalForm => false;
public static bool HasClone(Mobile m)
{
return m_CloneCount.ContainsKey(m);
}
public static void AddClone(Mobile m)
{
if (m == null)
return;
m_CloneCount[m] = 1 + (m_CloneCount.TryGetValue(m, out int count) ? count : 0);
}
public static void RemoveClone(Mobile m)
{
if (m == null || !m_CloneCount.TryGetValue(m, out int count))
return;
if (count <= 1)
m_CloneCount.Remove(m);
else
m_CloneCount[m]--;
}
public override bool CheckCast()
{
if (Caster.Mounted)
{
Caster.SendLocalizedMessage(1063132); // You cannot use this ability while mounted.
return false;
}
if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(
1063133); // You cannot summon a mirror image because you have too many followers.
return false;
}
if (TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable)
{
return false;
}
public override void OnBeginCast()
{
base.OnBeginCast();
Caster.SendLocalizedMessage(1063134); // You begin to summon a mirror image of yourself.
}
public override void OnCast()
{
if (Caster.Mounted)
{
Caster.SendLocalizedMessage(1063132); // You cannot use this ability while mounted.
}
else if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(
1063133); // You cannot summon a mirror image because you have too many followers.
}
else if (TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
}
else if (CheckSequence())
{
Caster.FixedParticles(0x376A, 1, 14, 0x13B5, EffectLayer.Waist);
Caster.PlaySound(0x511);
new Clone(Caster).MoveToWorld(Caster.Location, Caster.Map);
}
FinishSequence();
}
}
}
namespace Server.Mobiles
{
public class Clone : BaseCreature
{
private Mobile m_Caster;
public Clone(Mobile caster) : base(AIType.AI_Melee, FightMode.None, 10, 1, 0.2, 0.4)
{
m_Caster = caster;
Body = caster.Body;
Hue = caster.Hue;
Female = caster.Female;
Name = caster.Name;
NameHue = caster.NameHue;
Title = caster.Title;
Kills = caster.Kills;
HairItemID = caster.HairItemID;
HairHue = caster.HairHue;
FacialHairItemID = caster.FacialHairItemID;
FacialHairHue = caster.FacialHairHue;
for (int i = 0; i < caster.Skills.Length; ++i)
{
Skills[i].Base = caster.Skills[i].Base;
Skills[i].Cap = caster.Skills[i].Cap;
}
for (int i = 0; i < caster.Items.Count; i++) AddItem(CloneItem(caster.Items[i]));
Warmode = true;
Summoned = true;
SummonMaster = caster;
ControlOrder = OrderType.Follow;
ControlTarget = caster;
TimeSpan duration = TimeSpan.FromSeconds(30 + caster.Skills.Ninjitsu.Fixed / 40);
new UnsummonTimer(caster, this, duration).Start();
SummonEnd = DateTime.UtcNow + duration;
MirrorImage.AddClone(m_Caster);
}
public Clone(Serial serial) : base(serial)
{
}
protected override BaseAI ForcedAI => new CloneAI(this);
public override bool DeleteCorpseOnDeath => true;
public override bool IsDispellable => false;
public override bool Commandable => false;
public override bool IsHumanInTown()
{
return false;
}
private Item CloneItem(Item item)
{
Item newItem = new Item(item.ItemID);
newItem.Hue = item.Hue;
newItem.Layer = item.Layer;
return newItem;
}
public override void OnDamage(int amount, Mobile from, bool willKill)
{
Delete();
}
public override void OnDelete()
{
Effects.SendLocationParticles(EffectItem.Create(Location, Map, EffectItem.DefaultDuration), 0x3728, 10, 15,
5042);
base.OnDelete();
}
public override void OnAfterDelete()
{
MirrorImage.RemoveClone(m_Caster);
base.OnAfterDelete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
writer.Write(m_Caster);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
m_Caster = reader.ReadMobile();
MirrorImage.AddClone(m_Caster);
}
}
}
namespace Server.Mobiles
{
public class CloneAI : BaseAI
{
public CloneAI(Clone m) : base(m)
{
m.CurrentSpeed = m.ActiveSpeed;
}
public override bool CanDetectHidden => false;
public override bool Think()
{
// Clones only follow their owners
Mobile master = m_Mobile.SummonMaster;
if (master?.Map == m_Mobile.Map && master?.InRange(m_Mobile, m_Mobile.RangePerception) == true)
{
int iCurrDist = (int)m_Mobile.GetDistanceToSqrt(master);
bool bRun = iCurrDist > 5;
WalkMobileRange(master, 2, bRun, 0, 1);
}
else
{
WalkRandom(2, 2, 1);
}
return true;
}
}
}

View file

@ -0,0 +1,12 @@
namespace Server.Spells
{
public class NinjaMove : SpecialMove
{
public override SkillName MoveSkill => SkillName.Ninjitsu;
public override void CheckGain(Mobile m)
{
m.CheckSkill(MoveSkill, RequiredSkill - 12.5, RequiredSkill + 37.5); //Per five on friday 02/16/07
}
}
}

View file

@ -0,0 +1,100 @@
using Server.Mobiles;
namespace Server.Spells.Ninjitsu
{
public abstract class NinjaSpell : Spell
{
public NinjaSpell(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.Ninjitsu;
public override SkillName DamageSkill => SkillName.Ninjitsu;
public override bool RevealOnCast => false;
public override bool ClearHandsOnCast => false;
public override bool ShowHandMovement => false;
public override bool BlocksMovement => false;
//public override int CastDelayBase => 1;
public override int CastRecoveryBase => 7;
public static bool CheckExpansion(Mobile from)
{
return (from as PlayerMobile)?.NetState?.SupportsExpansion(Expansion.SE) == true;
}
public override bool CheckCast()
{
int mana = ScaleMana(RequiredMana);
if (!base.CheckCast())
return false;
if (!CheckExpansion(Caster))
{
Caster.SendLocalizedMessage(1063456); // You must upgrade to Samurai Empire in order to use that ability.
return false;
}
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
string args = $"{RequiredSkill.ToString("F1")}\t{CastSkill.ToString()}\t ";
Caster.SendLocalizedMessage(1063013,
args); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
return true;
}
public override bool CheckFizzle()
{
int mana = ScaleMana(RequiredMana);
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
Caster.SendLocalizedMessage(1063352,
RequiredSkill.ToString("F1")); // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
return false;
}
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 (!base.CheckFizzle())
return false;
Caster.Mana -= mana;
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 0;
}
}
}

View file

@ -0,0 +1,114 @@
using System;
using Server.Factions;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
using Server.Regions;
using Server.SkillHandlers;
using Server.Targeting;
namespace Server.Spells.Ninjitsu
{
public class Shadowjump : NinjaSpell, ISpellTargetingPoint3D
{
private static SpellInfo m_Info = new SpellInfo(
"Shadowjump", null,
-1,
9002
);
public Shadowjump(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 50.0;
public override int RequiredMana => 15;
public override bool BlockedByAnimalForm => false;
public override bool CheckCast()
{
PlayerMobile pm = Caster as PlayerMobile; // IsStealthing should be moved to Server.Mobiles
if (!pm.IsStealthing)
{
Caster.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable)
{
return false;
}
public override void OnCast()
{
Caster.SendLocalizedMessage(1063088); // You prepare to perform a Shadowjump.
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, 11);
}
public void Target(IPoint3D p)
{
IPoint3D orig = p;
Map map = Caster.Map;
SpellHelper.GetSurfaceTop(ref p);
Point3D from = Caster.Location;
Point3D to = new Point3D(p);
PlayerMobile pm = Caster as PlayerMobile; // IsStealthing should be moved to Server.Mobiles
if (!pm.IsStealthing)
{
Caster.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
}
else if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (WeightOverloading.IsOverloaded(Caster))
{
Caster.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
}
else if (!SpellHelper.CheckTravel(Caster, TravelCheckType.TeleportFrom) ||
!SpellHelper.CheckTravel(Caster, map, to, TravelCheckType.TeleportTo))
{
}
else if (map == null || !map.CanSpawnMobile(p.X, p.Y, p.Z))
{
Caster.SendLocalizedMessage(502831); // Cannot teleport to that spot.
}
else if (SpellHelper.CheckMulti(to, map, true, 5))
{
Caster.SendLocalizedMessage(502831); // Cannot teleport to that spot.
}
else if (Region.Find(to, map).IsPartOf<HouseRegion>())
{
Caster.SendLocalizedMessage(502829); // Cannot teleport to that spot.
}
else if (CheckSequence())
{
SpellHelper.Turn(Caster, orig);
Mobile m = Caster;
m.Location = to;
m.ProcessDelta();
Effects.SendLocationParticles(EffectItem.Create(from, m.Map, EffectItem.DefaultDuration), 0x3728, 10, 10,
2023);
m.PlaySound(0x512);
Stealth.OnUse(m); // stealth check after the a jump
}
FinishSequence();
}
}
}

View file

@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using Server.SkillHandlers;
namespace Server.Spells.Ninjitsu
{
public class SurpriseAttack : NinjaMove
{
private static Dictionary<Mobile, SurpriseAttackInfo> m_Table = new Dictionary<Mobile, SurpriseAttackInfo>();
public override int BaseMana => 20;
public override double RequiredSkill => Core.ML ? 60.0 : 30.0;
public override TextDefinition AbilityMessage => new TextDefinition(1063128); // You prepare to surprise your prey.
public override bool ValidatesDuringHit => false;
public override bool Validate(Mobile from)
{
if (!from.Hidden || from.AllowedStealthSteps <= 0)
{
from.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.Validate(from);
}
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
bool valid = Validate(attacker) && CheckMana(attacker, true);
if (valid)
{
attacker.BeginAction<Stealth>();
Timer.DelayCall(TimeSpan.FromSeconds(5.0), delegate { attacker.EndAction<Stealth>(); });
}
return valid;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
//Validates before swing
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063129); // You catch your opponent off guard with your Surprise Attack!
defender.SendLocalizedMessage(1063130); // Your defenses are lowered as your opponent surprises you!
defender.FixedParticles(0x37B9, 1, 5, 0x26DA, 0, 3, EffectLayer.Head);
attacker.RevealingAction();
if (m_Table.TryGetValue(defender, out SurpriseAttackInfo info))
{
info.m_Timer?.Stop();
m_Table.Remove(defender);
}
int ninjitsu = attacker.Skills.Ninjitsu.Fixed;
int malus = ninjitsu / 60 + (int)Tracking.GetStalkingBonus(attacker, defender);
info = new SurpriseAttackInfo(defender, malus);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(8.0), EndSurprise, info);
m_Table[defender] = info;
CheckGain(attacker);
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063161); // You failed to properly use the element of surprise.
attacker.RevealingAction();
}
public static bool GetMalus(Mobile target, ref int malus)
{
if (!m_Table.TryGetValue(target, out SurpriseAttackInfo info))
return false;
malus = info.m_Malus;
return true;
}
private static void EndSurprise(SurpriseAttackInfo info)
{
info.m_Timer?.Stop();
info.m_Target.SendLocalizedMessage(1063131); // Your defenses have returned to normal.
m_Table.Remove(info.m_Target);
}
private class SurpriseAttackInfo
{
public int m_Malus;
public Mobile m_Target;
public Timer m_Timer;
public SurpriseAttackInfo(Mobile target, int effect)
{
m_Target = target;
m_Malus = effect;
}
}
}
}

View file

@ -0,0 +1,133 @@
using System;
using Server.Items;
namespace Server.Spells
{
public class Reagent
{
private static Type[] m_Types =
{
typeof(BlackPearl),
typeof(Bloodmoss),
typeof(Garlic),
typeof(Ginseng),
typeof(MandrakeRoot),
typeof(Nightshade),
typeof(SulfurousAsh),
typeof(SpidersSilk),
typeof(BatWing),
typeof(GraveDust),
typeof(DaemonBlood),
typeof(NoxCrystal),
typeof(PigIron),
typeof(Bone),
typeof(FertileDirt),
typeof(DragonsBlood),
typeof(DaemonBone)
};
public Type[] Types => m_Types;
public static Type BlackPearl
{
get => m_Types[0];
set => m_Types[0] = value;
}
public static Type Bloodmoss
{
get => m_Types[1];
set => m_Types[1] = value;
}
public static Type Garlic
{
get => m_Types[2];
set => m_Types[2] = value;
}
public static Type Ginseng
{
get => m_Types[3];
set => m_Types[3] = value;
}
public static Type MandrakeRoot
{
get => m_Types[4];
set => m_Types[4] = value;
}
public static Type Nightshade
{
get => m_Types[5];
set => m_Types[5] = value;
}
public static Type SulfurousAsh
{
get => m_Types[6];
set => m_Types[6] = value;
}
public static Type SpidersSilk
{
get => m_Types[7];
set => m_Types[7] = value;
}
public static Type BatWing
{
get => m_Types[8];
set => m_Types[8] = value;
}
public static Type GraveDust
{
get => m_Types[9];
set => m_Types[9] = value;
}
public static Type DaemonBlood
{
get => m_Types[10];
set => m_Types[10] = value;
}
public static Type NoxCrystal
{
get => m_Types[11];
set => m_Types[11] = value;
}
public static Type PigIron
{
get => m_Types[12];
set => m_Types[12] = value;
}
public static Type Bone
{
get => m_Types[13];
set => m_Types[13] = value;
}
public static Type FertileDirt
{
get => m_Types[14];
set => m_Types[14] = value;
}
public static Type DragonsBlood
{
get => m_Types[15];
set => m_Types[15] = value;
}
public static Type DaemonBone
{
get => m_Types[16];
set => m_Types[16] = value;
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using Server.Engines.ConPVP;
using Server.Targeting;
namespace Server.Spells.Second
{
public class AgilitySpell : MagerySpell, ISpellTargetingMobile
{
private static SpellInfo m_Info = new SpellInfo(
"Agility", "Ex Uus",
212,
9061,
Reagent.Bloodmoss,
Reagent.MandrakeRoot
);
public AgilitySpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle => SpellCircle.Second;
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
{
Caster.SendMessage(0x22, "You cannot cast this spell when in sudden death.");
return false;
}
return base.CheckCast();
}
public override void OnCast()
{
Caster.Target = new SpellTargetMobile(this, TargetFlags.Beneficial, Core.ML ? 10 : 12);
}
public void Target(Mobile m)
{
if (m == null)
return;
if (!Caster.CanSee(m))
Caster.SendLocalizedMessage(500237); // Target can not be seen.
else if (CheckBSequence(m))
{
SpellHelper.Turn(Caster, m);
SpellHelper.AddStatBonus(Caster, m, StatType.Dex);
m.FixedParticles(0x375A, 10, 15, 5010, EffectLayer.Waist);
m.PlaySound(0x1e7);
int percentage = (int)(SpellHelper.GetOffsetScalar(Caster, m, false) * 100);
TimeSpan length = SpellHelper.GetDuration(Caster, m);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.Agility, 1075841, length, m, percentage.ToString()));
}
FinishSequence();
}
}
}

Some files were not shown because too many files have changed in this diff Show more