#region References using System; using Server.Items; using Server.SkillHandlers; using Server.Spells; 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.Second; using Server.Spells.Seventh; using Server.Spells.Sixth; using Server.Spells.SkillMasteries; using Server.Spells.Spellweaving; using Server.Spells.Third; using Server.Targeting; #endregion namespace Server.Mobiles { public class MageAI : BaseAI { public virtual SkillName CastSkill { get { return SkillName.Magery; } } public virtual bool UsesMagery { get { return true; } } public virtual double HealChance { get { return .25; } } protected const double DispelChance = 0.75; // 75% chance to dispel at gm skill protected double TeleportChance { get { return m_Mobile.TeleportChance; } } private LandTarget m_RevealTarget; public DateTime NextCastTime { get; set; } public DateTime NextHealTime { get; set; } public Mobile LastTarget { get; set; } public Point3D LastTargetLoc { get; set; } public virtual bool SmartAI { get { return PetTrainingHelper.GetAbilityProfile(m_Mobile, true).HasAbility(MagicalAbility.MageryMastery); } } public MageAI(BaseCreature m) : base(m) { } public override bool Think() { if (m_Mobile.Deleted) return false; if (ProcessTarget()) return true; return base.Think(); } public virtual double ScaleByCastSkill(double v) { return m_Mobile.Skills[CastSkill].Value * v * 0.01; } public virtual double ScaleBySkill(double v, SkillName skill) { return v * m_Mobile.Skills[skill].Value / 100; } protected TimeSpan GetDelay(Spell spell) { var del = ScaleBySkill(3.0, spell != null ? spell.CastSkill : CastSkill); var min = 6.0 - (del * 0.75); var max = 6.0 - (del * 1.25); return TimeSpan.FromSeconds(min + ((max - min) * Utility.RandomDouble())); } protected virtual DateTime GetCastDelay(Spell spell) { if (UsesMagery) { var ts = !SmartAI && !(spell is DispelSpell) ? TimeSpan.FromSeconds(1) : m_Combo > -1 ? TimeSpan.FromSeconds(.5) : TimeSpan.FromSeconds(1.5); var delay = spell == null ? TimeSpan.FromSeconds(m_Mobile.ActiveSpeed * 2.0) : spell.GetCastDelay() + spell.GetCastRecovery() + ts; return DateTime.UtcNow + delay; } else { var delay = spell == null ? TimeSpan.FromSeconds(m_Mobile.ActiveSpeed * 2.0) : spell.GetCastDelay() + spell.GetCastRecovery() + GetDelay(spell); return DateTime.UtcNow + delay; } } public override bool DoActionWander() { if (SmartAI && m_Mobile.Skills[SkillName.Meditation].Base > 0 && m_Mobile.Mana < m_Mobile.ManaMax && !m_Mobile.Meditating) { m_Mobile.DebugSay("I am going to meditate"); m_Mobile.UseSkill(SkillName.Meditation); return true; } if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.DebugSay("I am going to attack {0}", m_Mobile.FocusMob.Name); m_Mobile.Combatant = m_Mobile.FocusMob; Action = ActionType.Combat; NextCastTime = DateTime.UtcNow; } else { m_Mobile.DebugSay("I am wandering"); m_Mobile.Warmode = false; base.DoActionWander(); if (Utility.RandomDouble() < 0.05) { var spell = CheckCastHealingSpell(); if (spell != null) spell.Cast(); } } return true; } public override bool DoActionCombat() { var c = m_Mobile.Combatant; m_Mobile.Warmode = true; if (m_Mobile.Target != null) ProcessTarget(); if (c == null || c.Deleted || !c.Alive || (c is Mobile && ((Mobile)c).IsDeadBondedPet) || !m_Mobile.CanSee(c) || !m_Mobile.CanBeHarmful(c, false) || c.Map != m_Mobile.Map) { // Our combatant is deleted, dead, hidden, or we cannot hurt them // Try to find another combatant if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.DebugSay("Something happened to my combatant, so I am going to fight {0}", m_Mobile.FocusMob.Name); m_Mobile.Combatant = c = m_Mobile.FocusMob; m_Mobile.FocusMob = null; } else { m_Mobile.DebugSay("Something happened to my combatant, and nothing is around. I am on guard."); Action = ActionType.Guard; return true; } } if (!m_Mobile.InLOS(c)) { m_Mobile.DebugSay("I can't see my target"); if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.DebugSay("I will switch to {0}", m_Mobile.FocusMob.Name); m_Mobile.Combatant = c = m_Mobile.FocusMob; m_Mobile.FocusMob = null; } } if (!Core.AOS && SmartAI && !m_Mobile.StunReady && m_Mobile.Skills[SkillName.Wrestling].Value >= 80.0 && m_Mobile.Skills[SkillName.Anatomy].Value >= 80.0) EventSink.InvokeStunRequest(new StunRequestEventArgs(m_Mobile)); if (!m_Mobile.InRange(c, m_Mobile.RangePerception)) { // They are somewhat far away, can we find something else? if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.Combatant = m_Mobile.FocusMob; m_Mobile.FocusMob = null; } else if (!m_Mobile.InRange(c, m_Mobile.RangePerception * 3)) { m_Mobile.Combatant = null; } c = m_Mobile.Combatant as Mobile; if (c == null) { m_Mobile.DebugSay("My combatant has fled, so I am on guard"); Action = ActionType.Guard; return true; } } if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee) { if (m_Mobile.Hits < m_Mobile.HitsMax * 20 / 100) { // We are low on health, should we flee? if (Utility.Random(100) <= Math.Max(10, 10 + c.Hits - m_Mobile.Hits)) { m_Mobile.DebugSay("I am going to flee from {0}", c.Name); Action = ActionType.Flee; return true; } } } if (m_Mobile.Spell == null && DateTime.UtcNow > NextCastTime && m_Mobile.InRange(c, Core.ML ? 10 : 12)) { Spell spell = null; if (m_Mobile.Poisoned) { spell = GetCureSpell(); } if (SmartAI && spell == null && !m_Mobile.InRange(c.Location, 3) && UsesMagery && c is Mobile && !m_Mobile.DisallowAllMoves && CheckCanCastMagery(3) && m_Mobile.ControlOrder == OrderType.Attack && m_Mobile.InRange(c.Location, 10) && 0.6 > Utility.RandomDouble()) { spell = new TeleportSpell(m_Mobile, null); } if (spell == null) { // We are ready to cast a spell spell = ChooseSpell(c); } m_Mobile.DebugSay(spell != null ? "Choosen Spell" : "No spell chosen"); RunTo(c); if (spell != null) spell.Cast(); NextCastTime = GetCastDelay(spell); } else { RunTo(c); } LastTarget = c as Mobile; LastTargetLoc = c.Location; return true; } public override bool DoActionGuard() { if (LastTarget != null && LastTarget.Hidden) { var map = m_Mobile.Map; if (map == null || !m_Mobile.InRange(LastTargetLoc, Core.ML ? 10 : 12)) { LastTarget = null; } else if (m_Mobile.Spell == null && DateTime.UtcNow > NextCastTime) { // virutal method needs to go in BaseAI TryReveal(); m_Mobile.DebugSay("I am going to reveal my last target"); } } if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.DebugSay("I am going to attack {0}", m_Mobile.FocusMob.Name); m_Mobile.Combatant = m_Mobile.FocusMob; Action = ActionType.Combat; } else { ProcessTarget(); var spell = CheckCastHealingSpell(); if (spell != null) spell.Cast(); base.DoActionGuard(); } return true; } public override bool DoActionFlee() { var c = m_Mobile.Combatant as Mobile; if ((m_Mobile.Mana > 20 || m_Mobile.Mana == m_Mobile.ManaMax) && m_Mobile.Hits > (m_Mobile.HitsMax / 2)) { // If I have a target, go back and fight them if (c != null && m_Mobile.GetDistanceToSqrt(c) <= m_Mobile.RangePerception * 2) { m_Mobile.DebugSay("I am stronger now, reengaging {0}", c.Name); Action = ActionType.Combat; } else { m_Mobile.DebugSay("I am stronger now, my guard is up"); Action = ActionType.Guard; } } else { base.DoActionFlee(); } return true; } public virtual void RunTo(IDamageable d) { if (!SmartAI) { if (!MoveTo(d, true, m_Mobile.RangeFight)) OnFailedMove(); return; } if (d is Mobile && (((Mobile)d).Paralyzed || ((Mobile)d).Frozen)) { if (m_Mobile.InRange(d, 1)) RunFrom(d); else if (!m_Mobile.InRange(d, m_Mobile.RangeFight > 2 ? m_Mobile.RangeFight : 2) && !MoveTo(d, true, 1)) OnFailedMove(); } else { if (!m_Mobile.InRange(d, m_Mobile.RangeFight)) { if (!MoveTo(d, true, 1)) OnFailedMove(); } else if (m_Mobile.InRange(d, m_Mobile.RangeFight - 1)) { RunFrom(d); } } } public void RunFrom(IDamageable d) { Run((Direction)((int)m_Mobile.GetDirectionTo(d) - 4) & Direction.Mask); } public virtual void OnFailedMove() { if (UsesMagery && !m_Mobile.DisallowAllMoves && (SmartAI ? Utility.Random(10) == 0 : ScaleByCastSkill(TeleportChance) > Utility.RandomDouble())) { if (m_Mobile.Target != null) m_Mobile.Target.Cancel(m_Mobile, TargetCancelType.Canceled); new TeleportSpell(m_Mobile, null).Cast(); m_Mobile.DebugSay("I am stuck, I'm going to try teleporting away"); } else if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.DebugSay("My move is blocked, so I am going to attack {0}", m_Mobile.FocusMob.Name); m_Mobile.Combatant = m_Mobile.FocusMob; Action = ActionType.Combat; } else { m_Mobile.DebugSay("I am stuck"); } } public void Run(Direction d) { if ((m_Mobile.Spell != null && m_Mobile.Spell.IsCasting) || m_Mobile.Paralyzed || m_Mobile.Frozen || m_Mobile.DisallowAllMoves) return; m_Mobile.Direction = d | Direction.Running; if (!DoMove(m_Mobile.Direction, true)) OnFailedMove(); } public virtual Spell ChooseSpell(IDamageable c) { if (c == null || !c.Alive) return null; Spell spell = null; if (!UsesMagery) { if (!(c is Mobile)) { m_Mobile.DebugSay("Just doing damage..."); spell = GetRandomDamageSpell(); } else { m_Mobile.DebugSay("Random combat spell..."); spell = RandomCombatSpell(); } return spell; } if (m_Mobile.Poisoned) // Top cast priority is cure { m_Mobile.DebugSay("I am going to cure myself"); spell = GetCureSpell(); } if (spell != null) return spell; var toDispel = FindDispelTarget(true); if (toDispel != null) // Something dispellable is attacking us { m_Mobile.DebugSay("I am going to dispel {0}", toDispel); spell = DoDispel(toDispel); } if (spell != null) return spell; if (c is Mobile && SmartAI && m_Combo != -1) // We are doing a spell combo { spell = DoCombo((Mobile)c); } else if (c is Mobile && SmartAI && (((Mobile)c).Spell is HealSpell || ((Mobile)c).Spell is GreaterHealSpell) && !((Mobile)c).Poisoned) // They have a heal spell out { spell = new PoisonSpell(m_Mobile, null); } else { spell = RandomCombatSpell(); } return spell; } protected virtual Spell CheckCastHealingSpell() { // Summoned creatures never heal themselves. if (m_Mobile.Summoned) return null; Spell spell = null; // If I'm poisoned, always attempt to cure. if (m_Mobile.Poisoned) { spell = GetCureSpell(); } if (spell != null) return spell; if (m_Mobile.Controlled && DateTime.UtcNow < NextHealTime) { return null; } if (m_Mobile.Hits == m_Mobile.HitsMax || (!SmartAI && ScaleByCastSkill(HealChance) < Utility.RandomDouble())) { return null; } if (Utility.Random(0, 4 + (m_Mobile.Hits == 0 ? m_Mobile.HitsMax : (m_Mobile.HitsMax / m_Mobile.Hits))) < 3) { return null; } spell = GetHealSpell(); double delay; if (m_Mobile.Int >= 500) delay = Utility.RandomMinMax(7, 10); else delay = Math.Sqrt(600 - m_Mobile.Int); NextHealTime = DateTime.UtcNow + TimeSpan.FromSeconds(delay); return spell; } public virtual Spell DoDispel(Mobile toDispel) { if (!SmartAI) { if (CheckCanCastMagery(6) && ScaleByCastSkill(DispelChance) > Utility.RandomDouble()) return new DispelSpell(m_Mobile, null); return null; } var spell = CheckCastHealingSpell(); if (spell == null && CheckCanCastMagery(6)) { spell = new DispelSpell(m_Mobile, null); } return spell; } public Mobile FindDispelTarget(bool activeOnly) { if (m_Mobile.Deleted || m_Mobile.Int < 95 || CanDispel(m_Mobile) || m_Mobile.AutoDispel) return null; if (activeOnly) { var aggressed = m_Mobile.Aggressed; var aggressors = m_Mobile.Aggressors; Mobile active = null; var activePrio = 0.0; var comb = m_Mobile.Combatant as Mobile; if (comb != null && !comb.Deleted && comb.Alive && !comb.IsDeadBondedPet && m_Mobile.InRange(comb, Core.ML ? 10 : 12) && CanDispel(comb)) { active = comb; activePrio = m_Mobile.GetDistanceToSqrt(comb); if (activePrio <= 2) return active; } for (var i = 0; i < aggressed.Count; ++i) { var info = aggressed[i]; var m = info.Defender; if (m != comb && m.Combatant == m_Mobile && m_Mobile.InRange(m, Core.ML ? 10 : 12) && CanDispel(m)) { var prio = m_Mobile.GetDistanceToSqrt(m); if (active == null || prio < activePrio) { active = m; activePrio = prio; if (activePrio <= 2) return active; } } } for (var i = 0; i < aggressors.Count; ++i) { var info = aggressors[i]; var m = info.Attacker; if (m != comb && m.Combatant == m_Mobile && m_Mobile.InRange(m, Core.ML ? 10 : 12) && CanDispel(m)) { var prio = m_Mobile.GetDistanceToSqrt(m); if (active == null || prio < activePrio) { active = m; activePrio = prio; if (activePrio <= 2) return active; } } } return active; } var map = m_Mobile.Map; if (map != null) { Mobile active = null, inactive = null; double actPrio = 0.0, inactPrio = 0.0; var comb = m_Mobile.Combatant as Mobile; if (comb != null && !comb.Deleted && comb.Alive && !comb.IsDeadBondedPet && CanDispel(comb)) { active = inactive = comb; actPrio = inactPrio = m_Mobile.GetDistanceToSqrt(comb); } IPooledEnumerable eable = m_Mobile.GetMobilesInRange(Core.ML ? 10 : 12); foreach (Mobile m in eable) { if (m != m_Mobile && CanDispel(m)) { var prio = m_Mobile.GetDistanceToSqrt(m); if (!activeOnly && (inactive == null || prio < inactPrio)) { inactive = m; inactPrio = prio; } if ((m_Mobile.Combatant == m || m.Combatant == m_Mobile) && (active == null || prio < actPrio)) { active = m; actPrio = prio; } } } eable.Free(); return active != null ? active : inactive; } return null; } public bool CanDispel(Mobile m) { return (m is BaseCreature && ((BaseCreature)m).Summoned && m_Mobile.CanBeHarmful(m, false) && !((BaseCreature)m).IsAnimatedDead); } public Spell CheckCastDispelField() { if (m_Mobile.Frozen || m_Mobile.Paralyzed) return null; var mana = m_Mobile.Mana; if (mana < 14) return null; var field = GetHarmfulFieldItem(); if (field != null) { m_Mobile.DebugSay("Found harmful field, attempting to dispel"); return new DispelFieldSpell(m_Mobile, null); } return null; } public Item GetHarmfulFieldItem() { if (m_Mobile.Map == null) return null; IPooledEnumerable eable = m_Mobile.Map.GetItemsInRange(m_Mobile.Location, 0); foreach (Item item in eable) { if (item is PoisonFieldSpell.InternalItem) { var field = (PoisonFieldSpell.InternalItem)item; if (field.Visible && field.Caster != null && (!Core.AOS || m_Mobile != field.Caster) && SpellHelper.ValidIndirectTarget(field.Caster, m_Mobile) && field.Caster.CanBeHarmful(m_Mobile, false)) { eable.Free(); return item; } } else if (item is ParalyzeFieldSpell.InternalItem) { var field = (ParalyzeFieldSpell.InternalItem)item; if (field.Visible && field.Caster != null && (!Core.AOS || m_Mobile != field.Caster) && SpellHelper.ValidIndirectTarget(field.Caster, m_Mobile) && field.Caster.CanBeHarmful(m_Mobile, false)) { eable.Free(); return item; } } else if (item is FireFieldSpell.FireFieldItem) { var field = (FireFieldSpell.FireFieldItem)item; if (field.Visible && field.Caster != null && (!Core.AOS || m_Mobile != field.Caster) && SpellHelper.ValidIndirectTarget(field.Caster, m_Mobile) && field.Caster.CanBeHarmful(m_Mobile, false)) { eable.Free(); return item; } } } eable.Free(); return null; } public virtual Spell RandomCombatSpell() { Spell spell = null; if (!UsesMagery) { spell = CheckCastHealingSpell(); if (spell != null) return spell; switch (Utility.Random(6)) { case 0: // Buff { m_Mobile.DebugSay("Cursing Thyself!"); spell = GetRandomBuffSpell(); break; } case 1: // Curse { m_Mobile.DebugSay("Cursing Thou!"); spell = GetRandomCurseSpell(); break; } case 2: case 3: case 4: case 5: // damage { m_Mobile.DebugSay("Just doing damage"); spell = GetRandomDamageSpell(); } break; } return spell; } var c = m_Mobile.Combatant as Mobile; if (c == null || !c.Alive) return null; if (!SmartAI) { spell = CheckCastHealingSpell(); if (spell != null) return spell; if (spell == null && m_Mobile.RawInt >= 80) spell = CheckCastDispelField(); switch (Utility.Random(15)) { case 0: case 1: // Poison them { if (c.Poisoned || !CheckCanCastMagery(3)) goto default; m_Mobile.DebugSay("Attempting to poison"); spell = new PoisonSpell(m_Mobile, null); break; } case 2: // Bless ourselves { if (!CheckCanCastMagery(3)) goto default; m_Mobile.DebugSay("Blessing myself"); spell = GetRandomBuffSpell(); //new BlessSpell(m_Mobile, null); break; } case 3: case 4: // Curse them { m_Mobile.DebugSay("Attempting to curse"); spell = GetRandomCurseSpell(); if (spell == null) goto default; break; } case 5: // Paralyze them { m_Mobile.DebugSay("Attempting to paralyze"); if (CheckCanCastMagery(5)) spell = new ParalyzeSpell(m_Mobile, null); else spell = GetRandomCurseSpell(); if (spell == null) goto default; break; } case 6: // Drain mana { m_Mobile.DebugSay("Attempting to drain mana"); spell = GetRandomManaDrainSpell(); if (spell == null) goto default; break; } default: // Damage them { m_Mobile.DebugSay("Just doing damage"); spell = GetRandomDamageSpell(); break; } } } else { if (m_Mobile.Hidden) return null; spell = CheckCastDispelField(); if (spell == null) spell = CheckCastHealingSpell(); if (spell == null && 0.05 >= Utility.RandomDouble()) spell = GetRandomBuffSpell(); else if (spell == null && m_Mobile.Followers + 1 < m_Mobile.FollowersMax && 0.05 >= Utility.RandomDouble()) spell = GetRandomSummonSpell(); else if (spell == null && 0.05 >= Utility.RandomDouble()) spell = GetRandomFieldSpell(); else if (spell == null && 0.05 >= Utility.RandomDouble()) spell = GetRandomManaDrainSpell(); if (spell != null) return spell; switch (Utility.Random(3)) { case 0: // Poison them { if (c.Poisoned) goto case 1; spell = new PoisonSpell(m_Mobile, null); break; } case 1: // Deal some damage { spell = GetRandomDamageSpell(); break; } default: // Set up a combo { if (m_Mobile.Mana > 15 && m_Mobile.Mana < 40) { if (c.Paralyzed && !c.Poisoned && !m_Mobile.Meditating) { m_Mobile.DebugSay("I am going to meditate"); m_Mobile.UseSkill(SkillName.Meditation); } else if (!c.Poisoned) { spell = new ParalyzeSpell(m_Mobile, null); } } else if (m_Mobile.Mana > 60) { if (Utility.RandomBool() && !c.Paralyzed && !c.Frozen && !c.Poisoned) { m_Combo = 0; spell = new ParalyzeSpell(m_Mobile, null); } else { m_Combo = 1; spell = new ExplosionSpell(m_Mobile, null); } } break; } } } return spell; } //TODO: This needs to be tested on EA /// /// Obsolete. Creatures don't fizzle based on spell, but cast spells depending on mana pool /// /// public virtual int GetMaxCircle() { return (int)((m_Mobile.Skills[SkillName.Magery].Value + 20.0) / (100.0 / 7.0)); } private readonly int[] m_ManaTable = {4, 6, 9, 11, 14, 20, 40, 50}; public virtual bool CheckCanCastMagery(int circle) { if (circle < 1) circle = 1; if (circle > 8) circle = 8; var skill = (100.0 / 7.0) * circle; return m_Mobile.Mana >= m_ManaTable[circle - 1] && (Core.SA || m_Mobile.Skills[SkillName.Magery].Value >= skill - 20); } public virtual Spell GetRandomDamageSpell() { int select; if (SmartAI) { if (m_Mobile.Mana > 100) { if (!SkillMasterySpell.HasSpell(m_Mobile, typeof(DeathRaySpell))) select = Utility.RandomMinMax(0, 4); else select = Utility.RandomMinMax(1, 4); } else if (CheckCanCastMagery(7)) { select = Utility.Random(5, 11); } else { select = Utility.Random(5, 8); } switch (select) { case 0: return new DeathRaySpell(m_Mobile, null); case 1: return new MindBlastSpell(m_Mobile, null); case 2: return new EnergyBoltSpell(m_Mobile, null); case 3: return new ExplosionSpell(m_Mobile, null); case 4: return new FlameStrikeSpell(m_Mobile, null); case 5: return new MagicArrowSpell(m_Mobile, null); case 6: return new HarmSpell(m_Mobile, null); case 7: return new FireballSpell(m_Mobile, null); case 8: return new LightningSpell(m_Mobile, null); case 9: case 10: case 11: return new ManaVampireSpell(m_Mobile, null); } } else { if (CheckCanCastMagery(8)) select = 8; else if (CheckCanCastMagery(6)) select = 6; else if (CheckCanCastMagery(5)) select = 5; else if (CheckCanCastMagery(4)) select = 4; else if (CheckCanCastMagery(3)) select = 3; else if (CheckCanCastMagery(2)) select = 2; else select = 1; switch (Utility.Random(select)) { default: case 0: return new MagicArrowSpell(m_Mobile, null); case 1: return new HarmSpell(m_Mobile, null); case 2: return new FireballSpell(m_Mobile, null); case 3: return new LightningSpell(m_Mobile, null); case 4: return new MindBlastSpell(m_Mobile, null); case 5: return new EnergyBoltSpell(m_Mobile, null); case 6: return new ExplosionSpell(m_Mobile, null); case 7: return new FlameStrikeSpell(m_Mobile, null); } } return null; } public virtual Spell GetRandomCurseSpell() { if (Utility.RandomBool() && CheckCanCastMagery(5)) return new CurseSpell(m_Mobile, null); switch (Utility.Random(3)) { default: case 0: return new WeakenSpell(m_Mobile, null); case 1: return new ClumsySpell(m_Mobile, null); case 2: return new FeeblemindSpell(m_Mobile, null); } } public virtual Spell GetRandomManaDrainSpell() { if (Utility.RandomBool() && CheckCanCastMagery(7)) return new ManaVampireSpell(m_Mobile, null); if ((CheckCanCastMagery(4))) return new ManaDrainSpell(m_Mobile, null); return null; } public virtual Spell GetRandomSummonSpell() { // TODO: I left this here if anyone wants summons from magery /*if (CheckCanCastMagery(8)) return new EnergyVortexSpell(m_Mobile, null); if (CheckCanCastMagery(5)) return new BladeSpiritsSpell(m_Mobile, null);*/ return null; } public virtual Spell GetRandomFieldSpell() { // I left this here if someone wants field spells /*bool pois = m_Mobile.Skills[SkillName.Poisoning].Value >= 80.0 || m_Mobile.HitPoison == Poison.Greater || m_Mobile.HitPoison == Poison.Lethal; if (pois && CheckCanCastMagery(5)) return new PoisonFieldSpell(m_Mobile, null); if (Utility.RandomBool() && CheckCanCastMagery(6)) return new ParalyzeFieldSpell(m_Mobile, null); if (CheckCanCastMagery(4)) return new FireFieldSpell(m_Mobile, null);*/ return null; } public virtual Spell GetRandomBuffSpell() { if (!BlessSpell.IsBlessed(m_Mobile) && CheckCanCastMagery(3)) return new BlessSpell(m_Mobile, null); if (!m_Mobile.Controlled && CheckCanCastMagery(6)) return new InvisibilitySpell(m_Mobile, null); return null; } public virtual Spell GetHealSpell() { Spell spell = null; if (m_Mobile.Hits < (m_Mobile.HitsMax - 50)) { if (CheckCanCastMagery(4)) { spell = new GreaterHealSpell(m_Mobile, null); } else { spell = new HealSpell(m_Mobile, null); } } else if (m_Mobile.Hits < (m_Mobile.HitsMax - 10)) { spell = new HealSpell(m_Mobile, null); } return spell; } public virtual Spell GetCureSpell() { if (SmartAI && m_Mobile.Poison.Level > 1 && CheckCanCastMagery(4)) { return new ArchCureSpell(m_Mobile, null); } if (CheckCanCastMagery(2)) { return new CureSpell(m_Mobile, null); } return null; } protected int m_Combo = -1; protected ComboType m_ComboType; protected enum ComboType { None, Exp_FS_Poison, Exp_MB_Poison, Exp_EB_Poison, Exp_FB_MA_Poison, Exp_FB_Poison_Light, Exp_FB_MA_Light, Exp_Poison_FB_Light } public virtual Spell DoCombo(Mobile c) { Spell spell = null; if (m_ComboType == ComboType.None) m_ComboType = (ComboType)Utility.RandomMinMax(1, 7); if (m_Combo == 1) { switch (m_ComboType) { case ComboType.Exp_FS_Poison: case ComboType.Exp_MB_Poison: case ComboType.Exp_EB_Poison: case ComboType.Exp_FB_MA_Poison: case ComboType.Exp_FB_Poison_Light: case ComboType.Exp_FB_MA_Light: case ComboType.Exp_Poison_FB_Light: spell = new ExplosionSpell(m_Mobile, null); break; } } else if (m_Combo == 2) { switch (m_ComboType) { case ComboType.Exp_FS_Poison: spell = new FlameStrikeSpell(m_Mobile, null); break; case ComboType.Exp_MB_Poison: spell = new MindBlastSpell(m_Mobile, null); break; case ComboType.Exp_EB_Poison: spell = new EnergyBoltSpell(m_Mobile, null); break; case ComboType.Exp_FB_MA_Poison: spell = new FireballSpell(m_Mobile, null); break; case ComboType.Exp_FB_Poison_Light: spell = new FireballSpell(m_Mobile, null); break; case ComboType.Exp_FB_MA_Light: spell = new FireballSpell(m_Mobile, null); break; case ComboType.Exp_Poison_FB_Light: spell = new PoisonSpell(m_Mobile, null); break; } } else if (m_Combo == 3) { switch (m_ComboType) { case ComboType.Exp_FS_Poison: case ComboType.Exp_MB_Poison: case ComboType.Exp_EB_Poison: spell = new PoisonSpell(m_Mobile, null); EndCombo(); return spell; case ComboType.Exp_FB_MA_Poison: spell = new MagicArrowSpell(m_Mobile, null); break; case ComboType.Exp_FB_Poison_Light: spell = new PoisonSpell(m_Mobile, null); break; case ComboType.Exp_FB_MA_Light: spell = new MagicArrowSpell(m_Mobile, null); break; case ComboType.Exp_Poison_FB_Light: spell = new FireballSpell(m_Mobile, null); break; } } else if (m_Combo == 4) { switch (m_ComboType) { case ComboType.Exp_FS_Poison: case ComboType.Exp_MB_Poison: case ComboType.Exp_EB_Poison: spell = new LightningSpell(m_Mobile, null); EndCombo(); return spell; case ComboType.Exp_FB_MA_Poison: spell = new PoisonSpell(m_Mobile, null); break; case ComboType.Exp_FB_Poison_Light: case ComboType.Exp_FB_MA_Light: case ComboType.Exp_Poison_FB_Light: spell = new LightningSpell(m_Mobile, null); EndCombo(); return spell; } } else if (m_Combo == 5) { switch (m_ComboType) { case ComboType.Exp_FS_Poison: case ComboType.Exp_MB_Poison: case ComboType.Exp_EB_Poison: case ComboType.Exp_FB_MA_Poison: case ComboType.Exp_FB_Poison_Light: case ComboType.Exp_FB_MA_Light: case ComboType.Exp_Poison_FB_Light: spell = new LightningSpell(m_Mobile, null); EndCombo(); return spell; } } m_Combo++; // Move to next spell if (spell == null) spell = new PoisonSpell(m_Mobile, null); return spell; } public virtual void EndCombo() { m_ComboType = ComboType.None; m_Combo = -1; } protected virtual bool ProcessTarget() { var targ = m_Mobile.Target; if (targ == null) return false; var harmful = (targ.Flags & TargetFlags.Harmful) != 0 || targ is HailStormSpell.InternalTarget || targ is WildfireSpell.InternalTarget; var beneficial = (targ.Flags & TargetFlags.Beneficial) != 0 || targ is ArchCureSpell.InternalTarget; if (UsesMagery) { var isDispel = (targ is DispelSpell.InternalTarget || targ is MassDispelSpell.InternalTarget); var isParalyze = (targ is ParalyzeSpell.InternalTarget); var isTeleport = (targ is TeleportSpell.InternalTarget); var isSummon = (targ is EnergyVortexSpell.InternalTarget || targ is BladeSpiritsSpell.InternalTarget || targ is NatureFurySpell.InternalTarget); var isField = (targ is FireFieldSpell.InternalTarget || targ is PoisonFieldSpell.InternalTarget || targ is ParalyzeFieldSpell.InternalTarget); var isAnimate = (targ is AnimateDeadSpell.InternalTarget); var isDispelField = (targ is DispelFieldSpell.InternalTarget); var teleportAway = false; if (isTeleport && m_Mobile.CanSwim) targ.Cancel(m_Mobile, TargetCancelType.Canceled); IDamageable toTarget = null; if (isDispel) { toTarget = FindDispelTarget(false); if (toTarget != null) RunTo(toTarget); } else if (isDispelField) { var field = GetHarmfulFieldItem(); if (field != null) targ.Invoke(m_Mobile, field); else targ.Cancel(m_Mobile, TargetCancelType.Canceled); } else if (isAnimate) { var corpse = FindCorpseToAnimate(); if (corpse != null) targ.Invoke(m_Mobile, corpse); } else { toTarget = m_Mobile.Combatant; if (toTarget != null) RunTo(toTarget); } if (isSummon && toTarget != null) { var failSafe = 0; var map = toTarget.Map; while (failSafe <= 25) { var x = Utility.RandomMinMax(toTarget.X - 2, toTarget.X + 2); var y = Utility.RandomMinMax(toTarget.Y - 2, toTarget.Y + 2); var z = toTarget.Z; var lt = new LandTarget(new Point3D(x, y, z), map); if (map.CanSpawnMobile(x, y, z)) { targ.Invoke(m_Mobile, lt); break; } failSafe++; } } else if (isField && toTarget != null) { var map = toTarget.Map; var x = m_Mobile.X; var y = m_Mobile.Y; var z = m_Mobile.Z; if (toTarget == null || m_Mobile.InRange(toTarget.Location, 3)) { targ.Invoke(m_Mobile, toTarget); return true; } var d = Utility.GetDirection(m_Mobile, toTarget); var dist = (int)m_Mobile.GetDistanceToSqrt(toTarget.Location) / 2; var p = m_Mobile.Location; switch ((int)d) { case (int)Direction.Running: case (int)Direction.North: y = p.Y - dist; break; case 129: case (int)Direction.Right: x = p.X + dist; y = p.Y - dist; break; case 130: case (int)Direction.East: x = p.X + dist; break; case 131: case (int)Direction.Down: x = p.X + dist; y = p.Y + dist; break; case 132: case (int)Direction.South: y = p.Y + dist; break; case 133: case (int)Direction.Left: x = p.X - dist; y = p.Y + dist; break; case 134: case (int)Direction.West: x = p.X - dist; break; case (int)Direction.ValueMask: case (int)Direction.Up: x = p.X - dist; y = p.Y - dist; break; } var lt = new LandTarget(new Point3D(x, y, z), map); targ.Invoke(m_Mobile, lt); } if (harmful && toTarget != null) { if ((targ.Range == -1 || m_Mobile.InRange(toTarget, targ.Range)) && m_Mobile.CanSee(toTarget) && m_Mobile.InLOS(toTarget)) { targ.Invoke(m_Mobile, toTarget); } else if (isDispel) { targ.Cancel(m_Mobile, TargetCancelType.Canceled); } } else if (beneficial) { targ.Invoke(m_Mobile, m_Mobile); } else if (isTeleport && toTarget != null) { var map = m_Mobile.Map; if (map == null) { targ.Cancel(m_Mobile, TargetCancelType.Canceled); return true; } int px, py; if (teleportAway) { var rx = m_Mobile.X - toTarget.X; var ry = m_Mobile.Y - toTarget.Y; var d = m_Mobile.GetDistanceToSqrt(toTarget); px = toTarget.X + (int)(rx * (10 / d)); py = toTarget.Y + (int)(ry * (10 / d)); } else { px = toTarget.X; py = toTarget.Y; } for (var i = 0; i < m_Offsets.Length; i += 2) { int x = m_Offsets[i], y = m_Offsets[i + 1]; var p = new Point3D(px + x, py + y, 0); var lt = new LandTarget(p, map); if ((targ.Range == -1 || m_Mobile.InRange(p, targ.Range)) && m_Mobile.InLOS(lt) && map.CanSpawnMobile(px + x, py + y, lt.Z) && !SpellHelper.CheckMulti(p, map)) { targ.Invoke(m_Mobile, lt); return true; } } var teleRange = targ.Range; if (teleRange < 0) teleRange = Core.ML ? 11 : 12; for (var i = 0; i < 10; ++i) { var randomPoint = new Point3D( m_Mobile.X - teleRange + Utility.Random(teleRange * 2 + 1), m_Mobile.Y - teleRange + Utility.Random(teleRange * 2 + 1), 0); var lt = new LandTarget(randomPoint, map); if (m_Mobile.InLOS(lt) && map.CanSpawnMobile(lt.X, lt.Y, lt.Z) && !SpellHelper.CheckMulti(randomPoint, map)) { targ.Invoke(m_Mobile, new LandTarget(randomPoint, map)); return true; } } targ.Cancel(m_Mobile, TargetCancelType.Canceled); } else { targ.Cancel(m_Mobile, TargetCancelType.Canceled); } return true; } else { var toTarget = m_Mobile.Combatant; if (toTarget != null) RunTo(toTarget); if (harmful && toTarget != null) { if ((targ.Range == -1 || m_Mobile.InRange(toTarget, targ.Range)) && m_Mobile.CanSee(toTarget) && m_Mobile.InLOS(toTarget)) { targ.Invoke(m_Mobile, toTarget); } else { targ.Cancel(m_Mobile, TargetCancelType.Canceled); } } else if (beneficial) { targ.Invoke(m_Mobile, m_Mobile); } else { targ.Cancel(m_Mobile, TargetCancelType.Canceled); } return true; } } public Item FindCorpseToAnimate() { IPooledEnumerable eable = m_Mobile.GetItemsInRange(12); foreach (Item item in eable) { var c = item as Corpse; if (c != null) { Type type = null; if (c.Owner != null) type = c.Owner.GetType(); var owner = c.Owner as BaseCreature; if ((c.ItemID < 0xECA || c.ItemID > 0xED5) && m_Mobile.InLOS(c) && !c.Channeled && type != typeof(PlayerMobile) && type != null && (owner == null || (!owner.Summoned && !owner.IsBonded))) { eable.Free(); return item; } } } eable.Free(); return null; } private static readonly int[] m_Offsets = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 0, 1, 1, -2, -2, -2, -1, -2, 0, -2, 1, -2, 2, -1, -2, -1, 2, 0, -2, 0, 2, 1, -2, 1, 2, 2, -2, 2, -1, 2, 0, 2, 1, 2, 2 }; public virtual void TryReveal() { if (!m_Mobile.Alive || m_Mobile.Map == null) return; m_RevealTarget = new LandTarget(LastTargetLoc, m_Mobile.Map); if (UsesMagery) { Spell spell = new RevealSpell(m_Mobile, null); if (spell.Cast()) LastTarget = null; // only do it once NextCastTime = DateTime.UtcNow + GetDelay(spell); } else if (m_Mobile.CanDetectHidden) { DetectHidden.OnUse(m_Mobile); if (m_Mobile.Target is DetectHidden.InternalTarget) { m_Mobile.Target.Invoke(m_Mobile, m_RevealTarget); } NextCastTime = DateTime.UtcNow + TimeSpan.FromSeconds(1); } } } }