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).
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue