ModernUO/Projects/UOContent/Mobiles/AI/MageAI.cs
Sergi Rosell 24ebce6f84
fix(ai): swap GetDelay bounds so min is the lower one (#2711)
GetDelay sets min to 6.0 - del * 0.75 and max to 6.0 - del * 1.25, so for any Magery above zero min is the larger value. The draw min + (max - min) * r still covers the right interval, but it runs downward and the names say the opposite.

Swap the two definitions so min is the lower bound. The interval and its uniform distribution do not change; for a given random draw the delay comes out mirrored.
2026-10-17 13:58:04 -07:00

1138 lines
32 KiB
C#

using System;
using Server.Items;
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;
namespace Server.Mobiles;
public class MageAI : BaseAI
{
private const double HealChance = 0.10; // 10% chance to heal at gm magery
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 const double InvisChance = 0.50; // 50% chance to invis at gm magery
public override double FleeHealthThreshold => 0.2; // 20% is default
public override double FleeChance => 0.1; // 10% is default
private static readonly int[] 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
];
protected int _combo = -1;
private Mobile _lastTarget;
private Point3D _lastTargetLoc;
private long _nextCastTime;
private long _nextHealTime;
private LandTarget _revealTarget;
public MageAI(BaseCreature m) : base(m)
{
}
public virtual bool SmartAI => Mobile is BaseVendor or BaseEscortable or Changeling;
public virtual bool IsNecromancer => Core.AOS && Mobile.Skills.Necromancy.Value > 50;
public override bool Think()
{
if (Mobile.Deleted)
{
return false;
}
return ProcessTarget() || base.Think();
}
public virtual double ScaleBySkill(double v, SkillName skill) => v * Mobile.Skills[skill].Value / 100;
public override bool DoActionWander()
{
if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true))
{
this.DebugSayFormatted($"I am going to attack {Mobile.FocusMob.Name}");
Mobile.Combatant = Mobile.FocusMob;
Action = ActionType.Combat;
_nextCastTime = Core.TickCount;
}
else if (SmartAI && Mobile.Mana < Mobile.ManaMax && !Mobile.Meditating)
{
DebugSay("I am going to meditate");
Mobile.UseSkill(SkillName.Meditation);
}
else
{
DebugSay("I am wandering");
Mobile.Warmode = false;
base.DoActionWander();
if (Utility.RandomDouble() < 0.05)
{
var spell = CheckCastHealingSpell();
spell?.Cast();
}
}
return true;
}
private Spell CheckCastHealingSpell()
{
// If I'm poisoned, always attempt to cure.
if (Mobile.Poisoned)
{
return new CureSpell(Mobile);
}
// Summoned creatures never heal themselves.
if (Mobile.Summoned || Mobile.Controlled && Core.TickCount - _nextHealTime < 0)
{
return null;
}
if (!SmartAI)
{
if (HealChance > 0 && ScaleBySkill(HealChance, SkillName.Magery) < Utility.RandomDouble())
{
return null;
}
}
else if (Utility.Random(0, 4 + (Mobile.Hits == 0 ? Mobile.HitsMax : Mobile.HitsMax / Mobile.Hits)) < 3)
{
return null;
}
Spell spell = null;
if (Mobile.Hits < Mobile.HitsMax - 50)
{
if (UseNecromancy())
{
Mobile.UseSkill(SkillName.SpiritSpeak);
}
else
{
spell = new GreaterHealSpell(Mobile);
}
}
else if (Mobile.Hits < Mobile.HitsMax - 10)
{
spell = new HealSpell(Mobile);
}
var delay = Mobile.Int >= 500 ? Utility.RandomMinMax(7, 10) : Math.Sqrt(600 - Mobile.Int);
_nextHealTime = Core.TickCount + (int)TimeSpan.FromSeconds(delay).TotalMilliseconds;
return spell;
}
public void RunTo(Mobile m)
{
if (!SmartAI)
{
if (!MoveTo(m, Mobile.RangeFight))
{
OnFailedMove();
}
return;
}
if (m.Paralyzed || m.Frozen)
{
if (Mobile.InRange(m, 1))
{
RunFrom(m);
}
else if (!Mobile.InRange(m, Math.Max(Mobile.RangeFight, 2)) && !MoveTo(m, 1))
{
OnFailedMove();
}
}
else if (!Mobile.InRange(m, Mobile.RangeFight))
{
if (!MoveTo(m, 1))
{
OnFailedMove();
}
}
else if (Mobile.InRange(m, Mobile.RangeFight - 1))
{
RunFrom(m);
}
}
public void RunFrom(Mobile m)
{
Run((Mobile.GetDirectionTo(m) - 4) & Direction.Mask);
}
public void OnFailedMove()
{
if (!Mobile.DisallowAllMoves && (SmartAI
? Utility.Random(4) == 0
: TeleportChance > 0 && ScaleBySkill(TeleportChance, SkillName.Magery) > Utility.RandomDouble()))
{
Mobile.Target?.Cancel(Mobile, TargetCancelType.Canceled);
new TeleportSpell(Mobile).Cast();
DebugSay("I am stuck, I'm going to try teleporting away");
}
else if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true))
{
this.DebugSayFormatted($"My move is blocked, so I am going to attack {Mobile.FocusMob.Name}");
Mobile.Combatant = Mobile.FocusMob;
Action = ActionType.Combat;
}
else
{
DebugSay("I am stuck");
}
}
public void Run(Direction d)
{
if (Mobile.Spell?.IsCasting == true || Mobile.Paralyzed || Mobile.Frozen ||
Mobile.DisallowAllMoves)
{
return;
}
Mobile.Direction = d | Direction.Running;
if (!DoMove(Mobile.Direction, true))
{
OnFailedMove();
}
}
public virtual bool UseNecromancy()
{
var magery = Mobile.Skills.Magery.BaseFixedPoint;
var necro = Mobile.Skills.Necromancy.BaseFixedPoint;
return IsNecromancer && Utility.Random(magery + necro) >= magery;
}
public virtual Spell GetRandomDamageSpell() =>
UseNecromancy() ? GetRandomDamageSpellNecro() : GetRandomDamageSpellMage();
public virtual Spell GetRandomDamageSpellNecro()
{
var bound = Mobile.Skills.Necromancy.Value >= 100 ? 5 : 3;
return Utility.Random(bound) switch
{
0 => new PainSpikeSpell(Mobile),
1 => new PoisonStrikeSpell(Mobile),
2 => new StrangleSpell(Mobile),
3 => new WitherSpell(Mobile),
_ => new VengefulSpiritSpell(Mobile)
};
}
public virtual Spell GetRandomDamageSpellMage()
{
var maxCircle = Math.Clamp((int)((Mobile.Skills.Magery.Value + 20.0) / (100.0 / 7.0)), 1, 8);
return Utility.Random(maxCircle * 2) switch
{
0 => new MagicArrowSpell(Mobile),
1 => new MagicArrowSpell(Mobile),
2 => new HarmSpell(Mobile),
3 => new HarmSpell(Mobile),
4 => new FireballSpell(Mobile),
5 => new FireballSpell(Mobile),
6 => new LightningSpell(Mobile),
7 => new LightningSpell(Mobile),
8 => new MindBlastSpell(Mobile),
9 => new MindBlastSpell(Mobile),
10 => new EnergyBoltSpell(Mobile),
11 => new ExplosionSpell(Mobile),
_ => new FlameStrikeSpell(Mobile)
};
}
public virtual Spell GetRandomCurseSpell() =>
UseNecromancy() ? GetRandomCurseSpellNecro() : GetRandomCurseSpellMage();
public virtual Spell GetRandomCurseSpellNecro()
{
return Utility.Random(4) switch
{
0 => new BloodOathSpell(Mobile),
1 => new CorpseSkinSpell(Mobile),
2 => new EvilOmenSpell(Mobile),
_ => new MindRotSpell(Mobile)
};
}
public virtual Spell GetRandomCurseSpellMage()
{
if (Mobile.Skills.Magery.Value >= 40.0 && Utility.Random(4) == 0)
{
return new CurseSpell(Mobile);
}
return Utility.Random(3) switch
{
0 => new WeakenSpell(Mobile),
1 => new ClumsySpell(Mobile),
_ => new FeeblemindSpell(Mobile)
};
}
public virtual Spell GetRandomManaDrainSpell()
{
if (Mobile.Skills.Magery.Value >= 80.0 && Utility.RandomBool())
{
return new ManaVampireSpell(Mobile);
}
return new ManaDrainSpell(Mobile);
}
public virtual Spell DoDispel(Mobile toDispel)
{
if (!SmartAI)
{
if (DispelChance > 0 && ScaleBySkill(DispelChance, SkillName.Magery) > Utility.RandomDouble())
{
return new DispelSpell(Mobile);
}
return null;
}
var spell = CheckCastHealingSpell();
if (spell != null)
{
return spell;
}
var distance = (int)Mobile.GetDistanceToSqrt(toDispel);
if (!Mobile.DisallowAllMoves && distance > 0 && Utility.Random(distance) == 0)
{
return new TeleportSpell(Mobile);
}
if (Utility.Random(3) == 0 && !Mobile.InRange(toDispel, 3) && !toDispel.Paralyzed && !toDispel.Frozen)
{
return new ParalyzeSpell(Mobile);
}
return new DispelSpell(Mobile);
}
public virtual Spell ChooseSpell(Mobile c)
{
Spell spell;
if (!SmartAI)
{
spell = CheckCastHealingSpell();
if (spell != null)
{
return spell;
}
if (IsNecromancer)
{
var psDamage =
(Mobile.Skills.SpiritSpeak.Value - c.Skills.MagicResist.Value) / 10 +
(c.Player ? 18 : 30);
if (psDamage > c.Hits)
{
return new PainSpikeSpell(Mobile);
}
}
switch (Utility.Random(16))
{
case 0:
case 1: // Poison them
{
if (c.Poisoned)
{
goto default;
}
DebugSay("Attempting to poison");
spell = new PoisonSpell(Mobile);
break;
}
case 2: // Bless ourselves
{
DebugSay("Blessing myself");
spell = new BlessSpell(Mobile);
break;
}
case 3:
case 4: // Curse them
{
DebugSay("Attempting to curse");
spell = GetRandomCurseSpell();
break;
}
case 5: // Paralyze them
{
if (c.Paralyzed || Mobile.Skills.Magery.Value <= 50.0)
{
goto default;
}
DebugSay("Attempting to paralyze");
spell = new ParalyzeSpell(Mobile);
break;
}
case 6: // Drain mana
{
DebugSay("Attempting to drain mana");
spell = GetRandomManaDrainSpell();
break;
}
case 7: // Invis ourselves
{
if (InvisChance <= 0 || ScaleBySkill(InvisChance, SkillName.Magery) <= Utility.RandomDouble())
{
goto default;
}
DebugSay("Attempting to invis myself");
spell = new InvisibilitySpell(Mobile);
break;
}
default: // Damage them
{
DebugSay("Just doing damage");
spell = GetRandomDamageSpell();
break;
}
}
}
else
{
spell = CheckCastHealingSpell();
if (spell == null)
{
switch (Utility.Random(3))
{
case 0: // Poison them
{
if (c.Poisoned)
{
goto case 1;
}
spell = new PoisonSpell(Mobile);
break;
}
case 1: // Deal some damage
{
spell = GetRandomDamageSpell();
break;
}
default: // Set up a combo
{
if (Mobile.Mana is > 15 and < 40)
{
if (c.Paralyzed && !c.Poisoned && !Mobile.Meditating)
{
DebugSay("I am going to meditate");
Mobile.UseSkill(SkillName.Meditation);
}
else if (!c.Poisoned)
{
spell = new ParalyzeSpell(Mobile);
}
}
else if (Mobile.Mana > 60)
{
if (Utility.RandomBool() && !c.Paralyzed && !c.Frozen && !c.Poisoned)
{
_combo = 0;
spell = new ParalyzeSpell(Mobile);
}
else
{
_combo = 1;
spell = new ExplosionSpell(Mobile);
}
}
break;
}
}
}
}
if (spell != null)
{
this.DebugSayFormatted($"Casting {spell.Name}");
}
else
{
DebugSay("I don't have a spell to use!");
}
return spell;
}
public virtual Spell DoCombo(Mobile c)
{
Spell spell = null;
if (_combo == 0)
{
spell = new ExplosionSpell(Mobile);
++_combo; // Move to next spell
}
else if (_combo == 1)
{
spell = new WeakenSpell(Mobile);
++_combo; // Move to next spell
}
else if (_combo == 2)
{
if (!c.Poisoned)
{
spell = new PoisonSpell(Mobile);
}
else if (IsNecromancer)
{
spell = new StrangleSpell(Mobile);
}
++_combo; // Move to next spell
}
if (_combo == 3 && spell == null)
{
switch (Utility.Random(IsNecromancer ? 4 : 3))
{
case 0:
{
if (c.Int < c.Dex)
{
spell = new FeeblemindSpell(Mobile);
}
else
{
spell = new ClumsySpell(Mobile);
}
++_combo; // Move to next spell
break;
}
case 1:
{
spell = new EnergyBoltSpell(Mobile);
_combo = -1; // Reset combo state
break;
}
case 2:
{
spell = new FlameStrikeSpell(Mobile);
_combo = -1; // Reset combo state
break;
}
default:
{
spell = new PainSpikeSpell(Mobile);
_combo = -1; // Reset combo state
break;
}
}
}
else if (_combo == 4 && spell == null)
{
spell = new MindBlastSpell(Mobile);
_combo = -1;
}
return spell;
}
private TimeSpan GetDelay(Spell spell)
{
if (SmartAI || spell is DispelSpell)
{
return TimeSpan.FromSeconds(Mobile.ActiveSpeed);
}
var del = ScaleBySkill(3.0, SkillName.Magery);
var min = 6.0 - del * 1.25;
var max = 6.0 - del * 0.75;
return TimeSpan.FromSeconds(min + (max - min) * Utility.RandomDouble());
}
public override bool DoActionCombat()
{
var c = Mobile.Combatant;
Mobile.Warmode = true;
if (c?.Deleted != false || !c.Alive || c.IsDeadBondedPet || !Mobile.CanSee(c) ||
!Mobile.CanBeHarmful(c, false) || c.Map != Mobile.Map)
{
// Our combatant is deleted, dead, hidden, or we cannot hurt them
// Try to find another combatant
if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true))
{
Mobile.Combatant = c = Mobile.FocusMob!;
this.DebugSayFormatted($"Something happened to my combatant, so I am going to fight {c.Name}");
Mobile.FocusMob = null;
}
else
{
DebugSay("Something happened to my combatant, and nothing is around. I am on guard.");
Action = ActionType.Guard;
return true;
}
}
if (!Mobile.InLOS(c))
{
DebugSay("I can't see my target");
if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true))
{
Mobile.Combatant = c = Mobile.FocusMob!;
this.DebugSayFormatted($"I will switch to {c.Name}");
Mobile.FocusMob = null;
}
}
if (!Core.AOS && SmartAI && !Mobile.StunReady && Mobile.Skills.Wrestling.Value >= 80.0 &&
Mobile.Skills.Anatomy.Value >= 80.0)
{
Fists.StunRequest(Mobile);
}
if (!Mobile.InRange(c, Mobile.RangePerception))
{
// They are somewhat far away, can we find something else?
if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true))
{
Mobile.Combatant = Mobile.FocusMob;
Mobile.FocusMob = null;
}
else if (!Mobile.InRange(c, Mobile.ChaseLeashRange))
{
Mobile.Combatant = null;
}
c = Mobile.Combatant;
if (c == null)
{
DebugSay("My combatant has fled, so I am on guard");
Action = ActionType.Guard;
return true;
}
}
// Geometry (not hiding — CanSee passed above) is blocking the shot: close in until
// line of sight returns. Poisoned mages still fall through to cure.
if (!Mobile.Poisoned && Mobile.Spell?.IsCasting != true && !Mobile.InLOS(c))
{
DebugSay("I cannot see my target, moving to regain line of sight");
if (!MoveTo(c, 1))
{
OnFailedMove();
}
_lastTarget = c;
_lastTargetLoc = c.Location;
return true;
}
if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, c))
{
DebugSay("I used my abilities!");
}
else if (Mobile.Spell == null && Core.TickCount - _nextCastTime >= 0)
{
if (Mobile.Controlled && c == Mobile)
{
DebugSay("I should not attack myself!");
Mobile.Combatant = null;
Action = ActionType.Guard;
return true;
}
// We are ready to cast a spell
Spell spell;
var toDispel = FindDispelTarget(true);
if (Mobile.Poisoned) // Top cast priority is cure
{
DebugSay("I am going to cure myself");
spell = new CureSpell(Mobile);
}
else if (toDispel != null) // Something dispellable is attacking us
{
this.DebugSayFormatted($"I am going to dispel {toDispel}");
spell = DoDispel(toDispel); // May return null if dumb AI and doesn't have enough skill
}
else
{
spell = null;
}
spell ??= SmartAI switch
{
// We are doing a spell combo
true when _combo != -1 => DoCombo(c),
// They have a heal spell out
true when !c.Poisoned && c.Spell is HealSpell or GreaterHealSpell => new PoisonSpell(Mobile),
_ => ChooseSpell(c)
};
// Now we have a spell picked
var range = (spell as IRangedSpell)?.TargetRange ?? Mobile.RangePerception;
// Move first before casting
if (!SmartAI || toDispel == null)
{
RunTo(c);
}
else if (Mobile.InRange(toDispel, Math.Min(10, range)))
{
RunFrom(toDispel);
}
else if (!Mobile.InRange(toDispel, range))
{
RunTo(toDispel);
}
// After running, make sure we are still in range
if (spell == null || Mobile.InRange(c, range))
{
spell?.Cast();
// Even if we don't have a spell, delay the next potential cast
_nextCastTime = Core.TickCount + (int)GetDelay(spell).TotalMilliseconds;
}
}
else if (Mobile.Spell?.IsCasting != true)
{
RunTo(c);
}
if (Mobile.InRange(c, 1) || Mobile.Spell?.IsCasting == true || Core.TickCount - Mobile.LastMoveTime > 400)
{
Mobile.Direction = Mobile.GetDirectionTo(c);
}
_lastTarget = c;
_lastTargetLoc = c.Location;
return true;
}
public override bool DoActionGuard()
{
if (_lastTarget?.Hidden == true)
{
var map = Mobile.Map;
if (map == null || !Mobile.InRange(_lastTargetLoc, Mobile.RangePerception))
{
_lastTarget = null;
}
else if (Mobile.Spell == null && Core.TickCount - _nextCastTime >= 0)
{
DebugSay("I am going to reveal my last target");
_revealTarget = new LandTarget(_lastTargetLoc, map);
Spell spell = new RevealSpell(Mobile);
if (spell.Cast())
{
_lastTarget = null; // only do it once
}
_nextCastTime = Core.TickCount + (int)GetDelay(spell).TotalMilliseconds;
}
}
if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true))
{
this.DebugSayFormatted($"I am going to attack {Mobile.FocusMob.Name}");
Mobile.Combatant = Mobile.FocusMob;
Action = ActionType.Combat;
}
else
{
if (!Mobile.Controlled)
{
ProcessTarget();
var spell = CheckCastHealingSpell();
spell?.Cast();
}
base.DoActionGuard();
}
return true;
}
public Mobile FindDispelTarget(bool activeOnly)
{
if (Mobile.Deleted || Mobile.Int < 95 || CanDispel(Mobile) || Mobile.AutoDispel)
{
return null;
}
if (activeOnly)
{
var aggressed = Mobile.Aggressed;
var aggressors = Mobile.Aggressors;
Mobile active = null;
var activePrio = 0.0;
var comb = Mobile.Combatant;
if (comb?.Deleted == false && comb.Alive && !comb.IsDeadBondedPet &&
Mobile.InRange(comb, Mobile.RangePerception) && CanDispel(comb))
{
active = comb;
activePrio = 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 == Mobile && Mobile.InRange(m, Mobile.RangePerception) && CanDispel(m))
{
var prio = 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 == Mobile && Mobile.InRange(m, Mobile.RangePerception) && CanDispel(m))
{
var prio = Mobile.GetDistanceToSqrt(m);
if (active == null || prio < activePrio)
{
active = m;
activePrio = prio;
if (activePrio <= 2)
{
return active;
}
}
}
}
return active;
}
var map = Mobile.Map;
if (map != null)
{
Mobile active = null, inactive = null;
double actPrio = 0.0, inactPrio = 0.0;
var comb = Mobile.Combatant;
if (comb?.Deleted == false && comb.Alive && !comb.IsDeadBondedPet && CanDispel(comb))
{
active = inactive = comb;
actPrio = inactPrio = Mobile.GetDistanceToSqrt(comb);
}
foreach (var m in Mobile.GetMobilesInRange(Mobile.RangePerception))
{
if (m != Mobile && CanDispel(m))
{
var prio = Mobile.GetDistanceToSqrt(m);
if (inactive == null || prio < inactPrio)
{
inactive = m;
inactPrio = prio;
}
if ((Mobile.Combatant == m || m.Combatant == Mobile) && (active == null || prio < actPrio))
{
active = m;
actPrio = prio;
}
}
}
return active ?? inactive;
}
return null;
}
public bool CanDispel(Mobile m) =>
m is BaseCreature creature && creature.Summoned && creature.SummonMaster != Mobile &&
Mobile.CanBeHarmful(creature, false) && !creature.IsAnimatedDead;
private bool ProcessTarget()
{
var targ = Mobile.Target;
if (targ == null)
{
return false;
}
var spellTarg = targ as ISpellTarget<Mobile>;
var isReveal = spellTarg?.Spell is RevealSpell;
var isDispel = spellTarg?.Spell is DispelSpell;
var isParalyze = spellTarg?.Spell is ParalyzeSpell;
var isTeleport = spellTarg?.Spell is TeleportSpell;
var isInvisible = spellTarg?.Spell is InvisibilitySpell;
var teleportAway = false;
Mobile toTarget;
if (isInvisible)
{
toTarget = Mobile;
}
else if (isDispel)
{
toTarget = FindDispelTarget(false);
if (!SmartAI && toTarget != null)
{
RunTo(toTarget);
}
else if (toTarget != null && Mobile.InRange(toTarget, 10))
{
RunFrom(toTarget);
}
}
else if (SmartAI && (isParalyze || isTeleport))
{
toTarget = FindDispelTarget(true);
if (toTarget == null)
{
toTarget = Mobile.Combatant;
if (toTarget != null)
{
RunTo(toTarget);
}
}
else if (Mobile.InRange(toTarget, 10))
{
RunFrom(toTarget);
teleportAway = true;
}
else
{
teleportAway = true;
}
}
else
{
toTarget = Mobile.Combatant;
if (toTarget != null)
{
// Without line of sight the stand-off is pointless — close in so the held
// target can be invoked.
if (!Mobile.InLOS(toTarget))
{
MoveTo(toTarget, 1);
}
else
{
RunTo(toTarget);
}
}
}
if ((targ.Flags & TargetFlags.Harmful) != 0 && toTarget != null)
{
if ((targ.Range == -1 || Mobile.InRange(toTarget, targ.Range)) && Mobile.CanSee(toTarget) &&
Mobile.InLOS(toTarget))
{
targ.Invoke(Mobile, toTarget);
}
else if (isDispel)
{
targ.Cancel(Mobile, TargetCancelType.Canceled);
}
}
else if ((targ.Flags & TargetFlags.Beneficial) != 0)
{
targ.Invoke(Mobile, Mobile);
}
else if (isReveal && _revealTarget != null)
{
targ.Invoke(Mobile, _revealTarget);
}
else
{
var map = Mobile.Map;
if (map != null && isTeleport && toTarget != null)
{
var teleRange = targ.Range >= 0 ? targ.Range :
Core.ML ? 11 : 12;
int px, py;
if (teleportAway)
{
var rx = Mobile.X - toTarget.X;
var ry = Mobile.Y - toTarget.Y;
var d = 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 < Offsets.Length; i += 2)
{
int x = Offsets[i], y = Offsets[i + 1];
var p = new Point3D(px + x, py + y, 0);
var lt = new LandTarget(p, map);
if ((targ.Range == -1 || Mobile.InRange(p, targ.Range)) && Mobile.InLOS(lt) &&
map.CanSpawnMobile(px + x, py + y, lt.Z) && !SpellHelper.CheckMulti(p, map))
{
targ.Invoke(Mobile, lt);
return true;
}
}
for (var i = 0; i < 10; ++i)
{
var randomPoint = new Point3D(
Mobile.X - teleRange + Utility.Random(teleRange * 2 + 1),
Mobile.Y - teleRange + Utility.Random(teleRange * 2 + 1),
0
);
var lt = new LandTarget(randomPoint, map);
if (Mobile.InLOS(lt) && map.CanSpawnMobile(lt.X, lt.Y, lt.Z) &&
!SpellHelper.CheckMulti(randomPoint, map))
{
targ.Invoke(Mobile, new LandTarget(randomPoint, map));
return true;
}
}
}
targ.Cancel(Mobile, TargetCancelType.Canceled);
}
return true;
}
}