#region References using System; using Server.Spells; using Server.Spells.Fifth; using Server.Spells.First; using Server.Spells.Fourth; using Server.Spells.Necromancy; using Server.Spells.Second; using Server.Spells.Seventh; using Server.Spells.Sixth; using Server.Spells.Third; using Server.Targeting; #endregion namespace Server.Mobiles { public class SpellbinderAI : BaseAI { 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 }; private const double HealChance = 0.05; // 5% chance to heal at gm necromancy, uses spirit speak healing private const double TeleportChance = 0.05; // 5% chance to teleport at gm magery private const double DispelChance = 0.75; // 75% chance to dispel at gm magery private DateTime m_NextCastTime; private DateTime m_NextHealTime = DateTime.UtcNow; public SpellbinderAI(BaseCreature m) : base(m) { } public override bool Think() { if (m_Mobile.Deleted) return false; var targ = m_Mobile.Target; if (targ != null) { ProcessTarget(targ); return true; } return base.Think(); } public virtual double ScaleByNecromancy(double v) { return m_Mobile.Skills[SkillName.Necromancy].Value * v * 0.01; } public virtual double ScaleByMagery(double v) { return m_Mobile.Skills[SkillName.Magery].Value * v * 0.01; } public override bool DoActionWander() { 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; m_NextCastTime = DateTime.UtcNow; } else if (m_Mobile.Mana < m_Mobile.ManaMax) { m_Mobile.DebugSay("I am going to meditate"); m_Mobile.UseSkill(SkillName.Meditation); } else { m_Mobile.DebugSay("I am wandering"); m_Mobile.Warmode = false; base.DoActionWander(); if (m_Mobile.Poisoned) { new CureSpell(m_Mobile, null).Cast(); } else if (!m_Mobile.Summoned) { if (m_Mobile.Hits < (m_Mobile.HitsMax - 50)) { if (!new GreaterHealSpell(m_Mobile, null).Cast()) new HealSpell(m_Mobile, null).Cast(); } else if (m_Mobile.Hits < (m_Mobile.HitsMax - 10)) { new HealSpell(m_Mobile, null).Cast(); } } } return true; } public void RunTo(Mobile m) { if (m.Paralyzed || m.Frozen) { if (m_Mobile.InRange(m, 1)) RunFrom(m); else if (!m_Mobile.InRange(m, m_Mobile.RangeFight > 2 ? m_Mobile.RangeFight : 2) && !MoveTo(m, true, 1)) OnFailedMove(); } else { if (!m_Mobile.InRange(m, m_Mobile.RangeFight)) { if (!MoveTo(m, true, 1)) OnFailedMove(); } else if (m_Mobile.InRange(m, m_Mobile.RangeFight - 1)) { RunFrom(m); } } } public void RunFrom(IDamageable m) { Run((Direction)((int)m_Mobile.GetDirectionTo(m) - 4) & Direction.Mask); } public void OnFailedMove() { if (!m_Mobile.DisallowAllMoves && ScaleByMagery(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.CanMove || 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 GetRandomCurseSpell() { var necro = (int)((m_Mobile.Skills[SkillName.Necromancy].Value + 50.0) / (100.0 / 7.0)); var mage = (int)((m_Mobile.Skills[SkillName.Magery].Value + 50.0) / (100.0 / 7.0)); if (mage < 1) mage = 1; if (necro < 1) necro = 1; if (m_Mobile.Skills[SkillName.Necromancy].Value > 30 && Utility.Random(necro) > Utility.Random(mage)) { switch (Utility.Random(necro - 5)) { case 0: case 1: return new CorpseSkinSpell(m_Mobile, null); case 2: case 3: return new MindRotSpell(m_Mobile, null); default: return new MindRotSpell(m_Mobile, null); } } if (Utility.RandomBool() && mage > 3) 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()) { if (m_Mobile.Skills[SkillName.Magery].Value >= 80.0) return new ManaVampireSpell(m_Mobile, null); } return new ManaDrainSpell(m_Mobile, null); } public virtual Spell DoDispel(Mobile toDispel) { if (ScaleByMagery(DispelChance) > Utility.RandomDouble()) return new DispelSpell(m_Mobile, null); return ChooseSpell(toDispel); } public virtual Spell ChooseSpell(IDamageable c) { var spell = CheckCastHealingSpell(); if (spell != null) return spell; if (!(c is Mobile)) { return null; } var mob = c as Mobile; var damage = ((m_Mobile.Skills[SkillName.SpiritSpeak].Value - mob.Skills[SkillName.MagicResist].Value) / 10) + (mob.Player ? 18 : 30); if (damage > c.Hits) spell = new ManaDrainSpell(m_Mobile, null); switch (Utility.Random(16)) { case 0: case 1: case 2: // Poison them { m_Mobile.DebugSay("Attempting to BloodOath"); if (!mob.Poisoned) spell = new BloodOathSpell(m_Mobile, null); break; } case 3: // Bless ourselves. { m_Mobile.DebugSay("Blessing myself"); spell = new BlessSpell(m_Mobile, null); break; } case 4: case 5: case 6: // Curse them. { m_Mobile.DebugSay("Attempting to curse"); spell = GetRandomCurseSpell(); break; } case 7: // Paralyze them. { m_Mobile.DebugSay("Attempting to paralyze"); if (m_Mobile.Skills[SkillName.Magery].Value > 50.0) spell = new ParalyzeSpell(m_Mobile, null); break; } case 8: // Drain mana { m_Mobile.DebugSay("Attempting to drain mana"); spell = GetRandomManaDrainSpell(); break; } case 9: // Blood oath them { m_Mobile.DebugSay("Attempting to blood oath"); if (m_Mobile.Skills[SkillName.Necromancy].Value > 30 && BloodOathSpell.GetBloodOath(mob) != m_Mobile) spell = new BloodOathSpell(m_Mobile, null); break; } } return spell; } public override bool DoActionCombat() { var c = m_Mobile.Combatant; m_Mobile.Warmode = true; 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)) { if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.Combatant = c = m_Mobile.FocusMob; m_Mobile.FocusMob = null; } } if (!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; 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 > m_NextCastTime && m_Mobile.InRange(c, 12)) { // We are ready to cast a spell Spell spell = null; var toDispel = FindDispelTarget(true); if (m_Mobile.Poisoned) // Top cast priority is cure { m_Mobile.DebugSay("I am going to cure myself"); spell = new CureSpell(m_Mobile, null); } else if (toDispel != null) // Something dispellable is attacking us { m_Mobile.DebugSay("I am going to dispel {0}", toDispel); spell = DoDispel(toDispel); } else if (c is Mobile && (((Mobile)c).Spell is HealSpell || ((Mobile)c).Spell is GreaterHealSpell) && !((Mobile)c).Poisoned) // They have a heal spell out { spell = new BloodOathSpell(m_Mobile, null); } else { spell = ChooseSpell(c); } // Now we have a spell picked // Move first before casting if (toDispel != null) { if (m_Mobile.InRange(toDispel, 10)) RunFrom(toDispel); else if (!m_Mobile.InRange(toDispel, 12)) RunTo(toDispel); } else if (c is Mobile) { RunTo((Mobile)c); } if (spell != null) spell.Cast(); TimeSpan delay; if (spell is DispelSpell) delay = TimeSpan.FromSeconds(m_Mobile.ActiveSpeed); else delay = GetDelay(); m_NextCastTime = DateTime.UtcNow + delay; } else if (c is Mobile && (m_Mobile.Spell == null || !m_Mobile.Spell.IsCasting)) { RunTo((Mobile)c); } return true; } public override bool DoActionGuard() { 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 { if (m_Mobile.Poisoned) { new CureSpell(m_Mobile, null).Cast(); } else if (!m_Mobile.Summoned && ((ScaleByMagery(HealChance) > Utility.RandomDouble()))) { if (m_Mobile.Hits < (m_Mobile.HitsMax - 50)) { if (!new GreaterHealSpell(m_Mobile, null).Cast()) new HealSpell(m_Mobile, null).Cast(); } else if (m_Mobile.Hits < (m_Mobile.HitsMax - 10)) { new HealSpell(m_Mobile, null).Cast(); } else { base.DoActionGuard(); } } else { 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 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, 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, 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, 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(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); } private Spell CheckCastHealingSpell() { // If I'm poisoned, always attempt to cure. if (m_Mobile.Poisoned) return new CureSpell(m_Mobile, null); // Summoned creatures never heal themselves. if (m_Mobile.Summoned) return null; if (m_Mobile.Controlled) { if (DateTime.UtcNow < m_NextHealTime) return null; } if (ScaleByMagery(HealChance) < Utility.RandomDouble()) return null; Spell spell = null; if (m_Mobile.Hits < (m_Mobile.HitsMax - 50)) { spell = new GreaterHealSpell(m_Mobile, null); if (spell == null) spell = new HealSpell(m_Mobile, null); } else if (m_Mobile.Hits < (m_Mobile.HitsMax - 10)) spell = new HealSpell(m_Mobile, null); double delay; if (m_Mobile.Int >= 500) delay = Utility.RandomMinMax(7, 10); else delay = Math.Sqrt(600 - m_Mobile.Int); m_Mobile.UseSkill(SkillName.SpiritSpeak); m_NextHealTime = DateTime.UtcNow + TimeSpan.FromSeconds(delay); return spell; } private TimeSpan GetDelay() { var del = ScaleByMagery(3.0); var min = 6.0 - (del * 0.75); var max = 6.0 - (del * 1.25); return TimeSpan.FromSeconds(min + ((max - min) * Utility.RandomDouble())); } private void ProcessTarget(Target targ) { var isDispel = (targ is DispelSpell.InternalTarget); var isParalyze = (targ is ParalyzeSpell.InternalTarget); var isTeleport = (targ is TeleportSpell.InternalTarget); var teleportAway = false; Mobile toTarget; if (isDispel) { toTarget = FindDispelTarget(false); if (toTarget != null && m_Mobile.InRange(toTarget, 10)) RunFrom(toTarget); } else if (isParalyze || isTeleport) { toTarget = FindDispelTarget(true); if (toTarget == null) { toTarget = m_Mobile.Combatant as Mobile; if (toTarget != null) RunTo(toTarget); } else if (m_Mobile.InRange(toTarget, 10)) { RunFrom(toTarget); teleportAway = true; } else { teleportAway = true; } } else { toTarget = m_Mobile.Combatant as Mobile; if (toTarget != null) RunTo(toTarget); } if ((targ.Flags & TargetFlags.Harmful) != 0 && 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 ((targ.Flags & TargetFlags.Beneficial) != 0) { 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; } 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; } } var teleRange = targ.Range; if (teleRange < 0) teleRange = 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; } } targ.Cancel(m_Mobile, TargetCancelType.Canceled); } else { targ.Cancel(m_Mobile, TargetCancelType.Canceled); } } } }