Reorganizes Project (#41)
This commit is contained in:
parent
08bf44af9a
commit
3614a66aee
3499 changed files with 79 additions and 55 deletions
12
Projects/Scripts/Spells/Base/DisturbType.cs
Normal file
12
Projects/Scripts/Spells/Base/DisturbType.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace Server.Spells
|
||||
{
|
||||
public enum DisturbType
|
||||
{
|
||||
Unspecified,
|
||||
EquipRequest,
|
||||
UseRequest,
|
||||
Hurt,
|
||||
Kill,
|
||||
NewCast
|
||||
}
|
||||
}
|
||||
102
Projects/Scripts/Spells/Base/MagerySpell.cs
Normal file
102
Projects/Scripts/Spells/Base/MagerySpell.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
340
Projects/Scripts/Spells/Base/SpecialMove.cs
Normal file
340
Projects/Scripts/Spells/Base/SpecialMove.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
898
Projects/Scripts/Spells/Base/Spell.cs
Normal file
898
Projects/Scripts/Spells/Base/Spell.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Projects/Scripts/Spells/Base/SpellCircle.cs
Normal file
14
Projects/Scripts/Spells/Base/SpellCircle.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace Server.Spells
|
||||
{
|
||||
public enum SpellCircle
|
||||
{
|
||||
First,
|
||||
Second,
|
||||
Third,
|
||||
Fourth,
|
||||
Fifth,
|
||||
Sixth,
|
||||
Seventh,
|
||||
Eighth
|
||||
}
|
||||
}
|
||||
1388
Projects/Scripts/Spells/Base/SpellHelper.cs
Normal file
1388
Projects/Scripts/Spells/Base/SpellHelper.cs
Normal file
File diff suppressed because it is too large
Load diff
70
Projects/Scripts/Spells/Base/SpellInfo.cs
Normal file
70
Projects/Scripts/Spells/Base/SpellInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
171
Projects/Scripts/Spells/Base/SpellRegistry.cs
Normal file
171
Projects/Scripts/Spells/Base/SpellRegistry.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Projects/Scripts/Spells/Base/SpellState.cs
Normal file
13
Projects/Scripts/Spells/Base/SpellState.cs
Normal 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).
|
||||
}
|
||||
}
|
||||
144
Projects/Scripts/Spells/Bushido/Confidence.cs
Normal file
144
Projects/Scripts/Spells/Bushido/Confidence.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Projects/Scripts/Spells/Bushido/CounterAttack.cs
Normal file
108
Projects/Scripts/Spells/Bushido/CounterAttack.cs
Normal 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
215
Projects/Scripts/Spells/Bushido/Evasion.cs
Normal file
215
Projects/Scripts/Spells/Bushido/Evasion.cs
Normal 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
Projects/Scripts/Spells/Bushido/HonorableExecution.cs
Normal file
152
Projects/Scripts/Spells/Bushido/HonorableExecution.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Projects/Scripts/Spells/Bushido/LightningStrike.cs
Normal file
69
Projects/Scripts/Spells/Bushido/LightningStrike.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Projects/Scripts/Spells/Bushido/MomentumStrike.cs
Normal file
60
Projects/Scripts/Spells/Bushido/MomentumStrike.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Projects/Scripts/Spells/Bushido/SamuraiMove.cs
Normal file
12
Projects/Scripts/Spells/Bushido/SamuraiMove.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Projects/Scripts/Spells/Bushido/SamuraiSpell.cs
Normal file
126
Projects/Scripts/Spells/Bushido/SamuraiSpell.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Projects/Scripts/Spells/Chivalry/CleanseByFire.cs
Normal file
106
Projects/Scripts/Spells/Chivalry/CleanseByFire.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Projects/Scripts/Spells/Chivalry/CloseWounds.cs
Normal file
87
Projects/Scripts/Spells/Chivalry/CloseWounds.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Projects/Scripts/Spells/Chivalry/ConsecrateWeapon.cs
Normal file
111
Projects/Scripts/Spells/Chivalry/ConsecrateWeapon.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
105
Projects/Scripts/Spells/Chivalry/DispelEvil.cs
Normal file
105
Projects/Scripts/Spells/Chivalry/DispelEvil.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Projects/Scripts/Spells/Chivalry/DivineFury.cs
Normal file
73
Projects/Scripts/Spells/Chivalry/DivineFury.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Projects/Scripts/Spells/Chivalry/EnemyOfOne.cs
Normal file
77
Projects/Scripts/Spells/Chivalry/EnemyOfOne.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Projects/Scripts/Spells/Chivalry/HolyLight.cs
Normal file
65
Projects/Scripts/Spells/Chivalry/HolyLight.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
169
Projects/Scripts/Spells/Chivalry/NobleSacrifice.cs
Normal file
169
Projects/Scripts/Spells/Chivalry/NobleSacrifice.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
147
Projects/Scripts/Spells/Chivalry/PaladinSpell.cs
Normal file
147
Projects/Scripts/Spells/Chivalry/PaladinSpell.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Projects/Scripts/Spells/Chivalry/RemoveCurse.cs
Normal file
123
Projects/Scripts/Spells/Chivalry/RemoveCurse.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
142
Projects/Scripts/Spells/Chivalry/SacredJourney.cs
Normal file
142
Projects/Scripts/Spells/Chivalry/SacredJourney.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Projects/Scripts/Spells/Eighth/AirElemental.cs
Normal file
53
Projects/Scripts/Spells/Eighth/AirElemental.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Projects/Scripts/Spells/Eighth/EarthElemental.cs
Normal file
53
Projects/Scripts/Spells/Eighth/EarthElemental.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Projects/Scripts/Spells/Eighth/Earthquake.cs
Normal file
73
Projects/Scripts/Spells/Eighth/Earthquake.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Projects/Scripts/Spells/Eighth/EnergyVortex.cs
Normal file
69
Projects/Scripts/Spells/Eighth/EnergyVortex.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Projects/Scripts/Spells/Eighth/FireElemental.cs
Normal file
54
Projects/Scripts/Spells/Eighth/FireElemental.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Projects/Scripts/Spells/Eighth/Resurrection.cs
Normal file
79
Projects/Scripts/Spells/Eighth/Resurrection.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Projects/Scripts/Spells/Eighth/SummonDaemon.cs
Normal file
60
Projects/Scripts/Spells/Eighth/SummonDaemon.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Projects/Scripts/Spells/Eighth/WaterElemental.cs
Normal file
53
Projects/Scripts/Spells/Eighth/WaterElemental.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Projects/Scripts/Spells/Fifth/BladeSpirits.cs
Normal file
76
Projects/Scripts/Spells/Fifth/BladeSpirits.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Projects/Scripts/Spells/Fifth/DispelField.cs
Normal file
54
Projects/Scripts/Spells/Fifth/DispelField.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
171
Projects/Scripts/Spells/Fifth/Incognito.cs
Normal file
171
Projects/Scripts/Spells/Fifth/Incognito.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Projects/Scripts/Spells/Fifth/MagicReflect.cs
Normal file
145
Projects/Scripts/Spells/Fifth/MagicReflect.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Projects/Scripts/Spells/Fifth/MindBlast.cs
Normal file
127
Projects/Scripts/Spells/Fifth/MindBlast.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Projects/Scripts/Spells/Fifth/Paralyze.cs
Normal file
92
Projects/Scripts/Spells/Fifth/Paralyze.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
273
Projects/Scripts/Spells/Fifth/PoisonField.cs
Normal file
273
Projects/Scripts/Spells/Fifth/PoisonField.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Projects/Scripts/Spells/Fifth/SummonCreature.cs
Normal file
95
Projects/Scripts/Spells/Fifth/SummonCreature.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Projects/Scripts/Spells/First/Clumsy.cs
Normal file
62
Projects/Scripts/Spells/First/Clumsy.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Projects/Scripts/Spells/First/CreateFood.cs
Normal file
88
Projects/Scripts/Spells/First/CreateFood.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Projects/Scripts/Spells/First/Feeblemind.cs
Normal file
60
Projects/Scripts/Spells/First/Feeblemind.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Projects/Scripts/Spells/First/Heal.cs
Normal file
97
Projects/Scripts/Spells/First/Heal.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Projects/Scripts/Spells/First/MagicArrow.cs
Normal file
73
Projects/Scripts/Spells/First/MagicArrow.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Projects/Scripts/Spells/First/NightSight.cs
Normal file
75
Projects/Scripts/Spells/First/NightSight.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Projects/Scripts/Spells/First/ReactiveArmor.cs
Normal file
151
Projects/Scripts/Spells/First/ReactiveArmor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Projects/Scripts/Spells/First/Weaken.cs
Normal file
60
Projects/Scripts/Spells/First/Weaken.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
155
Projects/Scripts/Spells/Fourth/ArchCure.cs
Normal file
155
Projects/Scripts/Spells/Fourth/ArchCure.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Projects/Scripts/Spells/Fourth/ArchProtection.cs
Normal file
134
Projects/Scripts/Spells/Fourth/ArchProtection.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Projects/Scripts/Spells/Fourth/Curse.cs
Normal file
91
Projects/Scripts/Spells/Fourth/Curse.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
280
Projects/Scripts/Spells/Fourth/FireField.cs
Normal file
280
Projects/Scripts/Spells/Fourth/FireField.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Projects/Scripts/Spells/Fourth/GreaterHeal.cs
Normal file
85
Projects/Scripts/Spells/Fourth/GreaterHeal.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Projects/Scripts/Spells/Fourth/Lightning.cs
Normal file
69
Projects/Scripts/Spells/Fourth/Lightning.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Projects/Scripts/Spells/Fourth/ManaDrain.cs
Normal file
108
Projects/Scripts/Spells/Fourth/ManaDrain.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
Projects/Scripts/Spells/Fourth/Recall.cs
Normal file
143
Projects/Scripts/Spells/Fourth/Recall.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
207
Projects/Scripts/Spells/Initializer.cs
Normal file
207
Projects/Scripts/Spells/Initializer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Projects/Scripts/Spells/Mysticism/AnimatedWeaponSpell.cs
Normal file
66
Projects/Scripts/Spells/Mysticism/AnimatedWeaponSpell.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Projects/Scripts/Spells/Mysticism/EagleStrikeSpell.cs
Normal file
69
Projects/Scripts/Spells/Mysticism/EagleStrikeSpell.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
Projects/Scripts/Spells/Mysticism/HailStormSpell.cs
Normal file
121
Projects/Scripts/Spells/Mysticism/HailStormSpell.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Projects/Scripts/Spells/Mysticism/MysticSpell.cs
Normal file
93
Projects/Scripts/Spells/Mysticism/MysticSpell.cs
Normal 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 Mystic’s 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
Projects/Scripts/Spells/Mysticism/NetherCycloneSpell.cs
Normal file
130
Projects/Scripts/Spells/Mysticism/NetherCycloneSpell.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
217
Projects/Scripts/Spells/Mysticism/SpellPlagueSpell.cs
Normal file
217
Projects/Scripts/Spells/Mysticism/SpellPlagueSpell.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
163
Projects/Scripts/Spells/Mysticism/StoneFormSpell.cs
Normal file
163
Projects/Scripts/Spells/Mysticism/StoneFormSpell.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
374
Projects/Scripts/Spells/Necromancy/AnimateDeadSpell.cs
Normal file
374
Projects/Scripts/Spells/Necromancy/AnimateDeadSpell.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Projects/Scripts/Spells/Necromancy/BloodOathSpell.cs
Normal file
150
Projects/Scripts/Spells/Necromancy/BloodOathSpell.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
Projects/Scripts/Spells/Necromancy/CorpseSkin.cs
Normal file
131
Projects/Scripts/Spells/Necromancy/CorpseSkin.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Projects/Scripts/Spells/Necromancy/CurseWeapon.cs
Normal file
98
Projects/Scripts/Spells/Necromancy/CurseWeapon.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Projects/Scripts/Spells/Necromancy/EvilOmen.cs
Normal file
90
Projects/Scripts/Spells/Necromancy/EvilOmen.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
191
Projects/Scripts/Spells/Necromancy/Exorcism.cs
Normal file
191
Projects/Scripts/Spells/Necromancy/Exorcism.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Projects/Scripts/Spells/Necromancy/HorrificBeast.cs
Normal file
40
Projects/Scripts/Spells/Necromancy/HorrificBeast.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Projects/Scripts/Spells/Necromancy/LichForm.cs
Normal file
44
Projects/Scripts/Spells/Necromancy/LichForm.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
Projects/Scripts/Spells/Necromancy/MindRot.cs
Normal file
143
Projects/Scripts/Spells/Necromancy/MindRot.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Projects/Scripts/Spells/Necromancy/NecromancerSpell.cs
Normal file
61
Projects/Scripts/Spells/Necromancy/NecromancerSpell.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Projects/Scripts/Spells/Necromancy/PainSpike.cs
Normal file
115
Projects/Scripts/Spells/Necromancy/PainSpike.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Projects/Scripts/Spells/Necromancy/PoisonStrike.cs
Normal file
99
Projects/Scripts/Spells/Necromancy/PoisonStrike.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
215
Projects/Scripts/Spells/Necromancy/Strangle.cs
Normal file
215
Projects/Scripts/Spells/Necromancy/Strangle.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
197
Projects/Scripts/Spells/Necromancy/SummonFamiliar.cs
Normal file
197
Projects/Scripts/Spells/Necromancy/SummonFamiliar.cs
Normal 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Projects/Scripts/Spells/Necromancy/TransformationSpell.cs
Normal file
48
Projects/Scripts/Spells/Necromancy/TransformationSpell.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Projects/Scripts/Spells/Necromancy/VampiricEmbrace.cs
Normal file
53
Projects/Scripts/Spells/Necromancy/VampiricEmbrace.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Projects/Scripts/Spells/Necromancy/VengefulSpirit.cs
Normal file
75
Projects/Scripts/Spells/Necromancy/VengefulSpirit.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Projects/Scripts/Spells/Necromancy/Wither.cs
Normal file
107
Projects/Scripts/Spells/Necromancy/Wither.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Projects/Scripts/Spells/Necromancy/WraithForm.cs
Normal file
49
Projects/Scripts/Spells/Necromancy/WraithForm.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
616
Projects/Scripts/Spells/Ninjitsu/AnimalForm.cs
Normal file
616
Projects/Scripts/Spells/Ninjitsu/AnimalForm.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Projects/Scripts/Spells/Ninjitsu/Backstab.cs
Normal file
71
Projects/Scripts/Spells/Ninjitsu/Backstab.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
152
Projects/Scripts/Spells/Ninjitsu/DeathStrike.cs
Normal file
152
Projects/Scripts/Spells/Ninjitsu/DeathStrike.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Projects/Scripts/Spells/Ninjitsu/FocusAttack.cs
Normal file
66
Projects/Scripts/Spells/Ninjitsu/FocusAttack.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
Projects/Scripts/Spells/Ninjitsu/KiAttack.cs
Normal file
129
Projects/Scripts/Spells/Ninjitsu/KiAttack.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
269
Projects/Scripts/Spells/Ninjitsu/MirrorImage.cs
Normal file
269
Projects/Scripts/Spells/Ninjitsu/MirrorImage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Projects/Scripts/Spells/Ninjitsu/NinjaMove.cs
Normal file
12
Projects/Scripts/Spells/Ninjitsu/NinjaMove.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Projects/Scripts/Spells/Ninjitsu/NinjaSpell.cs
Normal file
100
Projects/Scripts/Spells/Ninjitsu/NinjaSpell.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Projects/Scripts/Spells/Ninjitsu/ShadowJump.cs
Normal file
114
Projects/Scripts/Spells/Ninjitsu/ShadowJump.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
113
Projects/Scripts/Spells/Ninjitsu/SurpriseAttack.cs
Normal file
113
Projects/Scripts/Spells/Ninjitsu/SurpriseAttack.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Projects/Scripts/Spells/Reagent.cs
Normal file
133
Projects/Scripts/Spells/Reagent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Projects/Scripts/Spells/Second/Agility.cs
Normal file
64
Projects/Scripts/Spells/Second/Agility.cs
Normal 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
Loading…
Add table
Add a link
Reference in a new issue