1300 lines
38 KiB
C#
1300 lines
38 KiB
C#
using System;
|
|
using Server;
|
|
using Server.Spells;
|
|
using Server.Network;
|
|
using Server.Mobiles;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Server.Engines.PartySystem;
|
|
using Server.Targeting;
|
|
using Server.Items;
|
|
using Server.Commands;
|
|
using System.Linq;
|
|
|
|
namespace Server.Spells.SkillMasteries
|
|
{
|
|
public abstract class SkillMasterySpell : Spell
|
|
{
|
|
public static void Initialize()
|
|
{
|
|
CommandSystem.Register("LearnAllMasteries", AccessLevel.GameMaster, LearnAllSpells);
|
|
CommandSystem.Register("RandomMastery", AccessLevel.GameMaster, RandomMastery);
|
|
}
|
|
|
|
public UpkeepTimer Timer { get; set; }
|
|
public Mobile Target { get; set; }
|
|
public List<Mobile> PartyList { get; set; }
|
|
public DateTime Expires { get; set; }
|
|
|
|
public virtual double RequiredSkill { get { return 90.0; } }
|
|
public virtual double UpKeep { get { return 0; } }
|
|
public virtual int RequiredMana { get { return 10; } }
|
|
public virtual bool PartyEffects { get { return false; } }
|
|
public virtual int DamageThreshold { get { return 45; } }
|
|
public virtual bool DamageCanDisrupt { get { return false; } }
|
|
public virtual double TickTime { get { return 2; } }
|
|
public virtual int PartyRange { get { return 12; } }
|
|
|
|
public virtual int UpkeepCancelMessage { get { return 1156111; } } // You do not have enough mana to keep your ability active.
|
|
public virtual int OutOfRangeMessage { get { return 1156098; } } // Your target is no longer in range of your ability.
|
|
public virtual int DisruptMessage { get { return 1156110; } } // Your ability was canceled.
|
|
|
|
public virtual bool CancelsWeaponAbility { get { return false; } }
|
|
public virtual bool CancelsSpecialMove { get { return CancelsWeaponAbility; } }
|
|
|
|
public virtual bool RevealOnTick { get { return true; } }
|
|
|
|
public virtual TimeSpan ExpirationPeriod { get { return TimeSpan.FromMinutes(30); } }
|
|
public override TimeSpan CastDelayBase { get { return TimeSpan.FromSeconds(2.25); } }
|
|
|
|
public virtual double BaseSkillBonus
|
|
{
|
|
get
|
|
{
|
|
if(Caster == null)
|
|
return 0.0;
|
|
|
|
return (Caster.Skills[CastSkill].Value + Caster.Skills[DamageSkill].Value + (GetMasteryLevel() * 40)) / 3;
|
|
}
|
|
}
|
|
|
|
public virtual double CollectiveBonus
|
|
{
|
|
get
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public override bool ClearHandsOnCast{ get{ return false; } }
|
|
public override bool BlocksMovement{ get{ return true; } }
|
|
|
|
public SkillMasterySpell( Mobile caster, Item scroll, SpellInfo info ) : base( caster, null, info )
|
|
{
|
|
}
|
|
|
|
public override bool CheckCast()
|
|
{
|
|
int mana = ScaleMana( RequiredMana );
|
|
|
|
if ( !base.CheckCast() )
|
|
return false;
|
|
|
|
if (IsInCooldown(Caster, this.GetType()))
|
|
return false;
|
|
|
|
if (Caster.Player && Caster.Skills[CastSkill].Value < RequiredSkill)
|
|
Caster.SendLocalizedMessage(1115709); // Your skills are not high enough to invoke this mastery ability.
|
|
else if (Caster is PlayerMobile && Caster.Skills.CurrentMastery != CastSkill)
|
|
Caster.SendLocalizedMessage(1115664); // You are not on the correct path for using this mastery ability.
|
|
else if (Caster is PlayerMobile && !MasteryInfo.HasLearned(Caster, CastSkill))
|
|
Caster.SendLocalizedMessage(1115664); // You are not on the correct path for using this mastery ability.
|
|
else if (Caster.Mana < mana)
|
|
Caster.SendLocalizedMessage(1060174, mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
|
|
else
|
|
{
|
|
if (CancelsWeaponAbility)
|
|
WeaponAbility.ClearCurrentAbility(Caster);
|
|
|
|
if (CancelsSpecialMove)
|
|
SpecialMove.ClearCurrentMove(Caster);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override void DoFizzle()
|
|
{
|
|
Caster.LocalOverheadMessage(MessageType.Regular, 0x3B2, 502632); // The spell fizzles.
|
|
|
|
if (Caster.Player)
|
|
{
|
|
Caster.FixedParticles(0x3735, 1, 30, 9503, EffectLayer.Waist);
|
|
|
|
//m_Caster.PlaySound(0x5C);
|
|
}
|
|
}
|
|
|
|
public override void GetCastSkills( out double min, out double max )
|
|
{
|
|
min = RequiredSkill;
|
|
max = RequiredSkill + 25.0;
|
|
}
|
|
|
|
public override int GetMana()
|
|
{
|
|
return RequiredMana;
|
|
}
|
|
|
|
public BaseWeapon GetWeapon()
|
|
{
|
|
return Caster.Weapon as BaseWeapon;
|
|
}
|
|
|
|
public bool CheckWeapon()
|
|
{
|
|
if (!Caster.Player)
|
|
return true;
|
|
|
|
BaseWeapon wep = GetWeapon();
|
|
|
|
if (CastSkill == SkillName.Poisoning && wep != null && !(wep is Fists))
|
|
return true;
|
|
|
|
return wep != null && wep.DefSkill == CastSkill;
|
|
}
|
|
|
|
public virtual bool OnTick()
|
|
{
|
|
UpdateParty();
|
|
|
|
if (RevealOnTick)
|
|
{
|
|
Caster.RevealingAction();
|
|
}
|
|
|
|
int upkeep = ScaleUpkeep();
|
|
|
|
if (0.10 > Utility.RandomDouble())
|
|
{
|
|
NegativeAttributes.OnCombatAction(Caster);
|
|
}
|
|
|
|
if ((Caster is PlayerMobile && Caster.NetState == null) || Expires < DateTime.UtcNow || !Caster.Alive || Caster.IsDeadBondedPet)
|
|
{
|
|
Expire();
|
|
}
|
|
else if (Target != null && !Target.Alive)
|
|
{
|
|
Expire();
|
|
}
|
|
else if (Target != null && !Caster.InRange(Target.Location, PartyRange))
|
|
{
|
|
Expire();
|
|
|
|
if (OutOfRangeMessage > 0)
|
|
Caster.SendLocalizedMessage(OutOfRangeMessage);
|
|
}
|
|
else if (Caster.Mana < upkeep)
|
|
{
|
|
if (UpkeepCancelMessage > 0)
|
|
Caster.SendLocalizedMessage(UpkeepCancelMessage);
|
|
|
|
Expire();
|
|
}
|
|
else if (Caster.Player && Caster.Skills[CastSkill].Value < RequiredSkill)
|
|
{
|
|
Expire();
|
|
}
|
|
else
|
|
{
|
|
DoEffects();
|
|
Caster.Mana -= upkeep;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected virtual void DoEffects()
|
|
{
|
|
}
|
|
|
|
public virtual int PropertyBonus()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public virtual int PropertyBonus2()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//Used for hits/stam/mana
|
|
public virtual int StatBonus()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public virtual void AddStatMods()
|
|
{
|
|
}
|
|
|
|
public virtual void RemoveStatMods()
|
|
{
|
|
}
|
|
|
|
public virtual void EndEffects()
|
|
{
|
|
}
|
|
|
|
public virtual int DamageBonus()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public virtual void OnDamaged(Mobile attacker, Mobile defender, DamageType type, ref int damage)
|
|
{
|
|
}
|
|
|
|
public virtual void OnHit(Mobile defender, ref int damage)
|
|
{
|
|
}
|
|
|
|
public virtual void OnGotHit(Mobile attacker, ref int damage)
|
|
{
|
|
}
|
|
|
|
public virtual void OnMiss(Mobile defender)
|
|
{
|
|
}
|
|
|
|
public virtual void OnGotMiss(Mobile attacker)
|
|
{
|
|
}
|
|
|
|
public virtual void OnParried(Mobile attacker)
|
|
{
|
|
}
|
|
|
|
public virtual void OnGotParried(Mobile defender)
|
|
{
|
|
}
|
|
|
|
public virtual void OnTargetDamaged(Mobile attacker, Mobile victim, DamageType type, ref int damage)
|
|
{
|
|
}
|
|
|
|
public virtual void OnWeaponRemoved(BaseWeapon weapon)
|
|
{
|
|
}
|
|
|
|
public virtual int ScaleUpkeep()
|
|
{
|
|
if (UpKeep == 0)
|
|
return 0;
|
|
|
|
double mod = CollectiveBonus;
|
|
|
|
double upkeep = UpKeep;
|
|
int mana = (int)(upkeep - ((upkeep * mod) / 4.5));
|
|
|
|
return ScaleMana(mana);
|
|
}
|
|
|
|
public virtual void Expire(bool disrupt = false)
|
|
{
|
|
if (Timer != null)
|
|
{
|
|
Timer.Stop();
|
|
Timer = null;
|
|
}
|
|
|
|
if(disrupt && DisruptMessage > 0)
|
|
Caster.SendLocalizedMessage(DisruptMessage);
|
|
|
|
Server.Timer.DelayCall(RemoveFromTable);
|
|
RemoveStatMods();
|
|
EndEffects();
|
|
|
|
Caster.Delta(MobileDelta.WeaponDamage);
|
|
|
|
if(Target != null)
|
|
Target.Delta(MobileDelta.WeaponDamage);
|
|
|
|
if (PartyList != null)
|
|
{
|
|
foreach(Mobile m in PartyList)
|
|
m.Delta(MobileDelta.WeaponDamage);
|
|
|
|
ColUtility.Free(PartyList);
|
|
}
|
|
|
|
OnExpire();
|
|
}
|
|
|
|
public virtual void OnExpire()
|
|
{
|
|
}
|
|
|
|
public virtual double DamageModifier(Mobile victim)
|
|
{
|
|
double dSkill = Caster.Skills[DamageSkill].Value;
|
|
double vSkill = GetResistSkill(victim);
|
|
|
|
double reduce = 1.0 - ((dSkill - vSkill) / dSkill);
|
|
|
|
if(reduce < 0) reduce = 0;
|
|
if(reduce > 1) reduce = 1;
|
|
|
|
return reduce;
|
|
}
|
|
|
|
public virtual bool CheckResisted(Mobile target)
|
|
{
|
|
int volumeMod = GetMasteryLevel();
|
|
volumeMod *= 2;
|
|
|
|
double n = GetResistPercent(target, volumeMod);
|
|
|
|
n /= 100.0;
|
|
|
|
if (n <= 0.0)
|
|
return false;
|
|
|
|
if (n >= 1.0)
|
|
return true;
|
|
|
|
int maxSkill = (1 + volumeMod) * 10;
|
|
maxSkill += (1 + (volumeMod / 6)) * 25;
|
|
|
|
if (target.Skills[SkillName.MagicResist].Value < maxSkill)
|
|
target.CheckSkill(SkillName.MagicResist, 0.0, target.Skills[SkillName.MagicResist].Cap);
|
|
|
|
return (n >= Utility.RandomDouble());
|
|
}
|
|
|
|
public virtual double GetResistPercent(Mobile target, int level)
|
|
{
|
|
double value = GetResistSkill(target);
|
|
double firstPercent = value / 5.0;
|
|
double secondPercent = value - (((Caster.Skills[CastSkill].Value - 20.0) / 5.0) + (1 + level) * 5.0);
|
|
|
|
return (firstPercent > secondPercent ? firstPercent : secondPercent) / 2.0;
|
|
}
|
|
|
|
public int GetMasteryLevel()
|
|
{
|
|
return (int)MasteryInfo.GetMasteryLevel(Caster, CastSkill);
|
|
}
|
|
|
|
public void UpdateParty()
|
|
{
|
|
UpdateParty(false);
|
|
}
|
|
|
|
public void UpdateParty(bool playersOnly)
|
|
{
|
|
var p = Party.Get(Caster);
|
|
|
|
if (PartyList == null)
|
|
{
|
|
PartyList = new List<Mobile>();
|
|
}
|
|
|
|
if (p != null)
|
|
{
|
|
foreach (var m in p.Members.Select(x => x.Mobile))
|
|
{
|
|
if (!PartyList.Contains(m) && (!playersOnly || m is PlayerMobile) && ValidPartyMember(m))
|
|
{
|
|
AddPartyMember(m);
|
|
}
|
|
else if (PartyList.Contains(m) && !ValidPartyMember(m))
|
|
{
|
|
RemovePartyMember(m);
|
|
}
|
|
else
|
|
{
|
|
UpdatePets(m);
|
|
}
|
|
}
|
|
}
|
|
else if (!PartyList.Contains(Caster))
|
|
{
|
|
AddPartyMember(Caster);
|
|
}
|
|
}
|
|
|
|
public virtual bool CheckPartyEffects(Mobile m, bool beneficial = false)
|
|
{
|
|
if (m == Caster)
|
|
return true;
|
|
|
|
if (Caster.IsBeneficialCriminal(m))
|
|
{
|
|
int casterNoto = Notoriety.Compute(Caster, m);
|
|
int mNoto = Notoriety.Compute(m, Caster);
|
|
|
|
if (casterNoto == Notoriety.Enemy || casterNoto != mNoto)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (beneficial)
|
|
Caster.DoBeneficial(m);
|
|
|
|
return true;
|
|
}
|
|
|
|
protected virtual bool ValidPartyMember(Mobile m)
|
|
{
|
|
return m.Alive && m.InRange(Caster, PartyRange) && m.Map == Caster.Map && CheckPartyEffects(m);
|
|
}
|
|
|
|
protected void AddPartyMember(Mobile m)
|
|
{
|
|
PartyList.Add(m);
|
|
AddPartyEffects(m);
|
|
|
|
if (m is PlayerMobile)
|
|
{
|
|
foreach (var pet in ((PlayerMobile)m).AllFollowers.Where(p => !PartyList.Contains(p) && ValidPartyMember(p)))
|
|
{
|
|
AddPartyMember(pet);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void RemovePartyMember(Mobile m)
|
|
{
|
|
PartyList.Remove(m);
|
|
RemovePartyEffects(m);
|
|
|
|
if (m is PlayerMobile)
|
|
{
|
|
foreach (var pet in ((PlayerMobile)m).AllFollowers.Where(p => PartyList.Contains(p)))
|
|
{
|
|
RemovePartyMember(pet);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdatePets(Mobile m)
|
|
{
|
|
if (m is PlayerMobile)
|
|
{
|
|
foreach (var pet in ((PlayerMobile)m).AllFollowers.Where(p => !PartyList.Contains(p) && ValidPartyMember(p)))
|
|
{
|
|
AddPartyMember(pet);
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void AddPartyEffects(Mobile m)
|
|
{
|
|
}
|
|
|
|
public virtual void RemovePartyEffects(Mobile m)
|
|
{
|
|
}
|
|
|
|
public static void OnPartyRemoved(Mobile m, bool disband)
|
|
{
|
|
foreach (var spell in GetSpells(s => s.PartyEffects && s.PartyList != null && (s.Caster == m || s.PartyList.Contains(m))))
|
|
{
|
|
if (disband)
|
|
{
|
|
spell.PartyList.IterateReverse(mob =>
|
|
{
|
|
if (mob != spell.Caster || (mob is BaseCreature && ((BaseCreature)mob).GetMaster() != mob))
|
|
{
|
|
spell.RemovePartyMember(mob);
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
spell.RemovePartyMember(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Dictionary<Mobile, List<SkillMasterySpell>> m_Table = new Dictionary<Mobile, List<SkillMasterySpell>>();
|
|
|
|
public static SkillMasterySpell GetHarmfulSpell(Mobile target, Type type)
|
|
{
|
|
foreach (Mobile m in m_Table.Keys)
|
|
{
|
|
foreach (SkillMasterySpell spell in m_Table[m])
|
|
{
|
|
if (spell != null && spell.GetType() == type && spell.Target == target)
|
|
return spell;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static bool HasHarmfulEffects(Mobile target, Type type)
|
|
{
|
|
foreach (Mobile m in m_Table.Keys)
|
|
{
|
|
foreach (SkillMasterySpell spell in m_Table[m])
|
|
{
|
|
if (spell != null && spell.GetType() == type && spell.Target == target)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static SkillMasterySpell GetSpell(Mobile from, Type type)
|
|
{
|
|
CheckTable(from);
|
|
|
|
if (m_Table.ContainsKey(from))
|
|
{
|
|
return m_Table[from].FirstOrDefault(spell => spell != null && spell.GetType() == type);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static TSpell GetSpell<TSpell>(Mobile m) where TSpell : SkillMasterySpell
|
|
{
|
|
if (m_Table.ContainsKey(m))
|
|
{
|
|
return m_Table[m].FirstOrDefault(sms => sms.GetType() == typeof(TSpell)) as TSpell;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static TSpell GetSpell<TSpell>(Mobile caster, Mobile target) where TSpell : SkillMasterySpell
|
|
{
|
|
if (m_Table.ContainsKey(caster))
|
|
{
|
|
return m_Table[caster].FirstOrDefault(sms => sms.GetType() == typeof(TSpell) && sms.Target == target) as TSpell;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static SkillMasterySpell GetSpell(Func<SkillMasterySpell, bool> predicate)
|
|
{
|
|
foreach (SkillMasterySpell spell in EnumerateAllSpells())
|
|
{
|
|
if (predicate != null && predicate(spell))
|
|
return spell;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static TSpell GetSpell<TSpell>(Func<SkillMasterySpell, bool> predicate) where TSpell : SkillMasterySpell
|
|
{
|
|
foreach (SkillMasterySpell spell in EnumerateAllSpells().Where(sp => sp.GetType() == typeof(TSpell)))
|
|
{
|
|
if (predicate != null && predicate(spell))
|
|
return spell as TSpell;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static IEnumerable<SkillMasterySpell> GetSpells(Func<SkillMasterySpell, bool> predicate)
|
|
{
|
|
foreach (SkillMasterySpell spell in EnumerateAllSpells())
|
|
{
|
|
if (spell != null && predicate != null && predicate(spell))
|
|
{
|
|
yield return spell;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<SkillMasterySpell> GetSpells(Mobile caster, Func<SkillMasterySpell, bool> predicate)
|
|
{
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(caster))
|
|
{
|
|
if (spell != null && predicate != null && predicate(spell))
|
|
{
|
|
yield return spell;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool HasSpell(Mobile from, Type type)
|
|
{
|
|
CheckTable(from);
|
|
|
|
if (m_Table.ContainsKey(from))
|
|
{
|
|
return m_Table[from].Any(spell => spell.GetType() == type);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool HasSpell<TSpell>(Mobile from) where TSpell : SkillMasterySpell
|
|
{
|
|
CheckTable(from);
|
|
|
|
if (m_Table.ContainsKey(from))
|
|
{
|
|
return m_Table[from].Any(spell => spell.GetType() == typeof(TSpell));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool UnderPartyEffects(Mobile from, Type type)
|
|
{
|
|
return GetSpellForParty(from, type) != null;
|
|
}
|
|
|
|
public static SkillMasterySpell GetSpellForParty(Mobile from, Type type)
|
|
{
|
|
CheckTable(from);
|
|
Mobile check = from;
|
|
|
|
if (from is BaseCreature && (((BaseCreature)from).Controlled || ((BaseCreature)from).Summoned) && ((BaseCreature)from).GetMaster() != null)
|
|
{
|
|
check = ((BaseCreature)from).GetMaster();
|
|
CheckTable(check);
|
|
}
|
|
|
|
//First checks the caster
|
|
if (m_Table.ContainsKey(check))
|
|
{
|
|
foreach (SkillMasterySpell spell in m_Table[check])
|
|
{
|
|
if (spell != null && spell.GetType() == type)
|
|
return spell;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Party p = Party.Get(check);
|
|
|
|
if (p != null)
|
|
{
|
|
foreach (PartyMemberInfo info in p.Members)
|
|
{
|
|
SkillMasterySpell spell = GetSpell(info.Mobile, type);
|
|
|
|
if (spell != null && spell.PartyEffects && from.InRange(info.Mobile.Location, spell.PartyRange) && spell.CheckPartyEffects(info.Mobile))
|
|
return spell;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static object _Lock = new object();
|
|
|
|
public static void CheckTable(Mobile m)
|
|
{
|
|
if (m == null)
|
|
return;
|
|
|
|
lock (_Lock)
|
|
{
|
|
if (m_Table.ContainsKey(m))
|
|
{
|
|
if (m_Table[m] == null || m_Table[m].Count == 0)
|
|
{
|
|
m_Table.Remove(m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<SkillMasterySpell> EnumerateAllSpells()
|
|
{
|
|
if (m_Table == null || m_Table.Count == 0)
|
|
yield break;
|
|
|
|
List<SkillMasterySpell> list = new List<SkillMasterySpell>();
|
|
|
|
lock (_Lock)
|
|
{
|
|
foreach (KeyValuePair<Mobile, List<SkillMasterySpell>> kvp in m_Table)
|
|
{
|
|
list.AddRange(kvp.Value);
|
|
}
|
|
}
|
|
|
|
foreach (SkillMasterySpell spell in list)
|
|
{
|
|
yield return spell;
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<SkillMasterySpell> EnumerateSpells(Mobile from, Type t = null)
|
|
{
|
|
if (m_Table == null || m_Table.Count == 0 || !m_Table.ContainsKey(from))
|
|
yield break;
|
|
|
|
List<SkillMasterySpell> list;
|
|
|
|
lock (_Lock)
|
|
{
|
|
list = m_Table[from];
|
|
|
|
if (list == null || list.Count == 0)
|
|
{
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
IEnumerable<SkillMasterySpell> e;
|
|
|
|
lock (_Lock)
|
|
{
|
|
e = list.Where(s => s.GetType() == t || t == null);
|
|
}
|
|
|
|
foreach (SkillMasterySpell spell in e)
|
|
{
|
|
yield return spell;
|
|
}
|
|
}
|
|
|
|
public static List<SkillMasterySpell> GetSpells(Mobile m)
|
|
{
|
|
if (m_Table != null && m_Table.ContainsKey(m))
|
|
{
|
|
return new List<SkillMasterySpell>(m_Table[m]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected void AddToTable(Mobile from, SkillMasterySpell spell)
|
|
{
|
|
lock (_Lock)
|
|
{
|
|
if (!m_Table.ContainsKey(from))
|
|
{
|
|
m_Table[from] = new List<SkillMasterySpell>();
|
|
}
|
|
|
|
if (!m_Table[from].Contains(spell))
|
|
{
|
|
m_Table[from].Add(spell);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void RemoveFromTable()
|
|
{
|
|
if(m_Table.ContainsKey(Caster) && m_Table[Caster].Contains(this))
|
|
{
|
|
lock (_Lock)
|
|
{
|
|
m_Table[Caster].Remove(this);
|
|
|
|
if (m_Table[Caster].Count == 0)
|
|
{
|
|
m_Table.Remove(Caster);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called in Aos.cs, should include all damage types
|
|
/// </summary>
|
|
/// <param name="victim"></param>
|
|
/// <param name="damager"></param>
|
|
/// <param name="damage"></param>
|
|
public static void OnDamage(Mobile victim, Mobile damager, DamageType type, ref int damage)
|
|
{
|
|
if(victim == null || damager == null)
|
|
return;
|
|
|
|
CheckTable(victim);
|
|
|
|
foreach (SkillMasterySpell sp in EnumerateSpells(victim))
|
|
{
|
|
if (sp.DamageCanDisrupt && damage > sp.DamageThreshold)
|
|
sp.Expire(true);
|
|
|
|
sp.OnDamaged(damager, victim, type, ref damage);
|
|
}
|
|
|
|
foreach (SkillMasterySpell sp in GetSpells(s => s.Target == victim))
|
|
{
|
|
sp.OnTargetDamaged(damager, victim, type, ref damage);
|
|
}
|
|
|
|
SkillMasteryMove move = SpecialMove.GetCurrentMove(victim) as SkillMasteryMove;
|
|
|
|
if (move != null)
|
|
move.OnDamaged(damager, victim, type, ref damage);
|
|
|
|
PerseveranceSpell preserve = SkillMasterySpell.GetSpellForParty(victim, typeof(PerseveranceSpell)) as PerseveranceSpell;
|
|
|
|
if (preserve != null)
|
|
preserve.AbsorbDamage(ref damage);
|
|
|
|
InspireSpell inspire = SkillMasterySpell.GetSpellForParty(damager, typeof(InspireSpell)) as InspireSpell;
|
|
|
|
if (inspire != null)
|
|
inspire.DoDamage(ref damage);
|
|
|
|
CombatTrainingSpell.CheckDamage(damager, victim, type, ref damage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called in BaseWeapon, intended as a melee/ranged hit
|
|
/// </summary>
|
|
/// <param name="attacker"></param>
|
|
/// <param name="defender"></param>
|
|
/// <param name="damage"></param>
|
|
public static void OnHit(Mobile attacker, Mobile defender, ref int damage)
|
|
{
|
|
if (attacker == null || defender == null)
|
|
return;
|
|
|
|
foreach(SkillMasterySpell spell in EnumerateSpells(attacker))
|
|
{
|
|
spell.OnHit(defender, ref damage);
|
|
}
|
|
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(defender))
|
|
{
|
|
spell.OnGotHit(defender, ref damage);
|
|
}
|
|
|
|
SkillMasteryMove move = SpecialMove.GetCurrentMove(defender) as SkillMasteryMove;
|
|
|
|
if (move != null)
|
|
move.OnGotHit(attacker, defender, ref damage);
|
|
|
|
if(attacker is BaseCreature || defender is BaseCreature)
|
|
CombatTrainingSpell.OnCreatureHit(attacker, defender, ref damage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called in BaseWeapon, intended as a melee/ranged miss
|
|
/// </summary>
|
|
/// <param name="attacker"></param>
|
|
/// <param name="defender"></param>
|
|
public static void OnMiss(Mobile attacker, Mobile defender)
|
|
{
|
|
if (attacker == null || defender == null)
|
|
return;
|
|
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(attacker))
|
|
{
|
|
spell.OnMiss(defender);
|
|
}
|
|
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(defender))
|
|
{
|
|
spell.OnGotMiss(attacker);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called in BaseWeapon, intended as a melee/ranged parry
|
|
/// </summary>
|
|
/// <param name="attacker"></param>
|
|
/// <param name="defender"></param>
|
|
public static void OnParried(Mobile attacker, Mobile defender)
|
|
{
|
|
if (attacker == null || defender == null)
|
|
return;
|
|
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(defender))
|
|
{
|
|
spell.OnParried(attacker);
|
|
}
|
|
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(attacker))
|
|
{
|
|
spell.OnGotParried(defender);
|
|
}
|
|
}
|
|
|
|
public static bool HasMastery(Mobile mobile, SkillName name)
|
|
{
|
|
Skill sk = mobile.Skills[name];
|
|
|
|
return sk.IsMastery && sk.VolumeLearned != 0;
|
|
}
|
|
|
|
public static bool SetActiveMastery(Mobile mobile, SkillName name)
|
|
{
|
|
return mobile.Skills[name].SetCurrent();
|
|
}
|
|
|
|
protected virtual void BeginTimer()
|
|
{
|
|
if(Timer != null)
|
|
{
|
|
Timer.Stop();
|
|
Timer = null;
|
|
}
|
|
|
|
Timer = new UpkeepTimer(this);
|
|
Timer.Start();
|
|
|
|
if(Expires < DateTime.UtcNow)
|
|
Expires = DateTime.UtcNow + ExpirationPeriod;
|
|
|
|
AddToTable(Caster, this);
|
|
AddStatMods();
|
|
|
|
if (RevealOnTick)
|
|
{
|
|
Caster.RevealingAction();
|
|
}
|
|
|
|
Caster.Delta(MobileDelta.WeaponDamage);
|
|
|
|
if(Target != null)
|
|
Target.Delta(MobileDelta.WeaponDamage);
|
|
|
|
if (PartyList != null)
|
|
{
|
|
foreach (Mobile m in PartyList)
|
|
m.Delta(MobileDelta.WeaponDamage);
|
|
}
|
|
}
|
|
|
|
public int GetWeaponSkill()
|
|
{
|
|
double swrd = Caster.Skills[SkillName.Swords].Value;
|
|
double fenc = Caster.Skills[SkillName.Fencing].Value;
|
|
double mcng = Caster.Skills[SkillName.Macing].Value;
|
|
double arch = Caster.Skills[SkillName.Archery].Value;
|
|
double thro = Caster.Skills[SkillName.Throwing].Value;
|
|
double wres = Caster.Skills[SkillName.Wrestling].Value;
|
|
|
|
double val = swrd;
|
|
|
|
if (fenc > val)
|
|
val = fenc;
|
|
|
|
if (mcng > val)
|
|
val = mcng;
|
|
|
|
if (arch > val)
|
|
val = arch;
|
|
|
|
if (thro > val)
|
|
val = thro;
|
|
|
|
if (wres > val)
|
|
val = wres;
|
|
|
|
return (int)val;
|
|
}
|
|
|
|
public static void OnWeaponRemoved(Mobile from, BaseWeapon weapon)
|
|
{
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(from))
|
|
{
|
|
spell.OnWeaponRemoved(weapon);
|
|
}
|
|
}
|
|
|
|
public static void CancelWeaponAbility(Mobile attacker)
|
|
{
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(attacker).Where(s => s.CancelsWeaponAbility))
|
|
{
|
|
spell.Expire();
|
|
}
|
|
}
|
|
|
|
public static void CancelSpecialMove(Mobile attacker)
|
|
{
|
|
foreach (SkillMasterySpell spell in EnumerateSpells(attacker).Where(s => s.CancelsSpecialMove))
|
|
{
|
|
spell.Expire();
|
|
}
|
|
}
|
|
|
|
private static Dictionary<SkillMasterySpell, DateTime> _Cooldown;
|
|
|
|
protected void AddToCooldown(TimeSpan ts)
|
|
{
|
|
if (_Cooldown == null)
|
|
_Cooldown = new Dictionary<SkillMasterySpell, DateTime>();
|
|
|
|
_Cooldown[this] = DateTime.UtcNow + ts;
|
|
|
|
Server.Timer.DelayCall(ts, () => RemoveFromCooldown(this.GetType(), Caster));
|
|
}
|
|
|
|
public static bool IsInCooldown(Mobile m, Type type, bool message = true)
|
|
{
|
|
CheckCooldown();
|
|
|
|
if (_Cooldown == null)
|
|
return false;
|
|
|
|
bool iscooling = false;
|
|
|
|
foreach (KeyValuePair<SkillMasterySpell, DateTime> kvp in _Cooldown)
|
|
{
|
|
SkillMasterySpell spell = kvp.Key;
|
|
DateTime dt = kvp.Value;
|
|
|
|
if (!iscooling && spell.GetType() == type && spell.Caster == m)
|
|
{
|
|
if (message)
|
|
{
|
|
double left = (dt - DateTime.UtcNow).TotalMinutes;
|
|
|
|
if (left > 1)
|
|
m.SendLocalizedMessage(1155787, ((int)left).ToString()); // You must wait ~1_minutes~ minutes before you can use this ability.
|
|
else
|
|
{
|
|
left = (_Cooldown[spell] - DateTime.UtcNow).TotalSeconds;
|
|
|
|
if(left > 0)
|
|
m.SendLocalizedMessage(1079335, left.ToString("F", System.Globalization.CultureInfo.InvariantCulture)); // You must wait ~1_seconds~ seconds before you can use this ability again.
|
|
}
|
|
}
|
|
|
|
iscooling = true;
|
|
}
|
|
}
|
|
|
|
return iscooling;
|
|
}
|
|
|
|
public static void RemoveFromCooldown(Type type, Mobile m)
|
|
{
|
|
if (_Cooldown == null)
|
|
return;
|
|
|
|
SkillMasterySpell spell = null;
|
|
|
|
foreach (KeyValuePair<SkillMasterySpell, DateTime> kvp in _Cooldown)
|
|
{
|
|
if (kvp.Key.GetType() == type && kvp.Key.Caster == m)
|
|
spell = kvp.Key;
|
|
}
|
|
|
|
if (spell != null)
|
|
_Cooldown.Remove(spell);
|
|
|
|
CheckCooldown();
|
|
}
|
|
|
|
public static void CheckCooldown()
|
|
{
|
|
if (_Cooldown == null)
|
|
return;
|
|
|
|
List<SkillMasterySpell> spells = new List<SkillMasterySpell>();
|
|
|
|
foreach (KeyValuePair<SkillMasterySpell, DateTime> kvp in _Cooldown)
|
|
{
|
|
if (kvp.Value < DateTime.UtcNow)
|
|
spells.Add(kvp.Key);
|
|
}
|
|
|
|
spells.ForEach(sp => _Cooldown.Remove(sp));
|
|
|
|
if (_Cooldown != null && _Cooldown.Count == 0)
|
|
_Cooldown = null;
|
|
}
|
|
|
|
public static int GetAttributeBonus(Mobile m, AosAttribute attr)
|
|
{
|
|
int value = 0;
|
|
SkillMasterySpell spell = null;
|
|
|
|
switch (attr)
|
|
{
|
|
case AosAttribute.AttackChance:
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(InspireSpell));
|
|
if (spell != null)
|
|
value += spell.PropertyBonus();
|
|
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(TribulationSpell));
|
|
if (spell != null)
|
|
value += spell.PropertyBonus();
|
|
|
|
value += FocusedEyeSpell.HitChanceBonus(m);
|
|
value += PlayingTheOddsSpell.HitChanceBonus(m);
|
|
value += CalledShotSpell.GetHitChanceBonus(m);
|
|
value += CombatTrainingSpell.GetHitChanceBonus(m);
|
|
|
|
value += MasteryInfo.SavingThrowChance(m, attr);
|
|
break;
|
|
case AosAttribute.DefendChance:
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(PerseveranceSpell));
|
|
|
|
if (spell != null)
|
|
value += spell.PropertyBonus();
|
|
|
|
if (Server.Spells.SkillMasteries.WhiteTigerFormSpell.IsActive(m))
|
|
value += 20;
|
|
|
|
value += MasteryInfo.SavingThrowChance(m, attr);
|
|
break;
|
|
case AosAttribute.RegenHits:
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(ResilienceSpell));
|
|
|
|
if (spell != null)
|
|
value += spell.PropertyBonus();
|
|
break;
|
|
case AosAttribute.RegenStam:
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(ResilienceSpell));
|
|
|
|
if (spell != null)
|
|
value += spell.PropertyBonus();
|
|
break;
|
|
case AosAttribute.RegenMana:
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(ResilienceSpell));
|
|
|
|
if (spell != null)
|
|
value += spell.PropertyBonus();
|
|
break;
|
|
case AosAttribute.WeaponDamage:
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(InspireSpell));
|
|
|
|
if (spell != null)
|
|
value += spell.DamageBonus();
|
|
|
|
value += MasteryInfo.SavingThrowChance(m, attr);
|
|
break;
|
|
case AosAttribute.SpellDamage:
|
|
spell = SkillMasterySpell.GetSpellForParty(m, typeof(InspireSpell));
|
|
|
|
if (spell != null)
|
|
value += spell.PropertyBonus();
|
|
break;
|
|
case AosAttribute.WeaponSpeed:
|
|
value += RampageSpell.GetBonus(m, RampageSpell.BonusType.SwingSpeed);
|
|
value += PlayingTheOddsSpell.SwingSpeedBonus(m);
|
|
value -= StaggerSpell.GetStagger(m);
|
|
break;
|
|
case AosAttribute.BonusStr:
|
|
value += MasteryInfo.SavingThrowChance(m, attr);
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public static int GetAttributeBonus(Mobile m, SAAbsorptionAttribute attr)
|
|
{
|
|
int value = 0;
|
|
|
|
switch (attr)
|
|
{
|
|
case SAAbsorptionAttribute.CastingFocus:
|
|
SkillMasterySpell spell = SkillMasterySpell.GetSpellForParty(m, typeof(PerseveranceSpell));
|
|
|
|
if (spell != null)
|
|
value += spell.PropertyBonus2();
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
protected virtual void OnTarget(object o)
|
|
{
|
|
}
|
|
|
|
public class MasteryTarget : Target
|
|
{
|
|
public SkillMasterySpell Owner { get; set; }
|
|
public bool AutoFinishSequence { get; set; }
|
|
|
|
public MasteryTarget(SkillMasterySpell spell, int range = 10, bool allowGround = false, TargetFlags flags = TargetFlags.None, bool autoEnd = true)
|
|
: base(range, allowGround, flags)
|
|
{
|
|
Owner = spell;
|
|
AutoFinishSequence = autoEnd;
|
|
}
|
|
|
|
protected override void OnTarget(Mobile from, object o)
|
|
{
|
|
if (o == null)
|
|
return;
|
|
|
|
if (!from.CanSee(o))
|
|
from.SendLocalizedMessage(500237); // Target can not be seen.
|
|
else
|
|
{
|
|
SpellHelper.Turn(from, o);
|
|
Owner.OnTarget(o);
|
|
}
|
|
}
|
|
|
|
protected override void OnTargetFinish(Mobile from)
|
|
{
|
|
if(AutoFinishSequence)
|
|
Owner.FinishSequence();
|
|
}
|
|
}
|
|
|
|
public class UpkeepTimer : Timer
|
|
{
|
|
private SkillMasterySpell m_Spell;
|
|
|
|
public SkillMasterySpell Spell { get { return m_Spell; } }
|
|
|
|
public UpkeepTimer(SkillMasterySpell spell) : base(TimeSpan.FromSeconds(spell.TickTime), TimeSpan.FromSeconds(spell.TickTime))
|
|
{
|
|
m_Spell = spell;
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
if(m_Spell != null)
|
|
m_Spell.OnTick();
|
|
}
|
|
}
|
|
|
|
[Usage("LearnAllMasteries")]
|
|
[Description("Select a target to learn all skill masteries.")]
|
|
public static void LearnAllSpells(CommandEventArgs e)
|
|
{
|
|
e.Mobile.SendMessage("Target the mobile who will learn all skill masteries.");
|
|
e.Mobile.BeginTarget(-1, false, TargetFlags.None, (mobile, targeted) =>
|
|
{
|
|
if (targeted is Mobile)
|
|
{
|
|
Mobile m = targeted as Mobile;
|
|
int count = 0;
|
|
|
|
foreach (Skill sk in m.Skills)
|
|
{
|
|
if (sk.IsMastery)
|
|
{
|
|
count++;
|
|
sk.VolumeLearned = 3;
|
|
}
|
|
}
|
|
|
|
e.Mobile.SendMessage("They have learned {0} masteries!", count.ToString());
|
|
}
|
|
});
|
|
}
|
|
|
|
[Usage("RandomMastery")]
|
|
[Description("Drops a random mastery primer on target.")]
|
|
public static void RandomMastery(CommandEventArgs e)
|
|
{
|
|
e.Mobile.BeginTarget(-1, false, TargetFlags.None, (mobile, targeted) =>
|
|
{
|
|
Item mastery = SkillMasteryPrimer.GetRandom();
|
|
|
|
if (targeted is Mobile)
|
|
{
|
|
Mobile m = targeted as Mobile;
|
|
|
|
m.Backpack.DropItem(mastery);
|
|
|
|
e.Mobile.SendMessage("A mastery has been added to your pack!");
|
|
}
|
|
else if (targeted is IPoint3D)
|
|
{
|
|
IPoint3D p = targeted as IPoint3D;
|
|
|
|
mastery.MoveToWorld(new Point3D(p.X, p.Y, p.Z), e.Mobile.Map);
|
|
}
|
|
else
|
|
mastery.Delete();
|
|
});
|
|
}
|
|
}
|
|
}
|