using System; using System.Collections; using System.Collections.Generic; using Server.Spells; using Server.Spells.Fifth; using Server.Spells.First; using Server.Spells.Fourth; 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 DateTime m_NextCastTime; private DateTime m_NextHealTime; public MageAI( BaseCreature m ) : base( m ) { } public override bool Think() { if( m_Mobile.Deleted ) return false; if( ProcessTarget() ) return true; else return base.Think(); } public virtual bool SmartAI { get { return ( m_Mobile is BaseVendor ); } } 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 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 ) ) { if( m_Mobile.Debug ) 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.Now; } else if( SmartAI && 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( ( Utility.RandomDouble() < .05 ) ) { Spell spell = CheckCastHealingSpell(); if( spell != null ) spell.Cast(); } } return true; } 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.Now < m_NextHealTime ) return null; } if( !SmartAI ) { if( ScaleByMagery( HealChance ) < Utility.RandomDouble() ) return null; } else { if( Utility.Random( 0, 4 + ( m_Mobile.Hits == 0 ? m_Mobile.HitsMax : ( m_Mobile.HitsMax / m_Mobile.Hits ) ) ) < 3 ) 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_NextHealTime = DateTime.Now + TimeSpan.FromSeconds( delay ); return spell; } public void RunTo( Mobile m ) { if( !SmartAI ) { if( !MoveTo( m, true, m_Mobile.RangeFight ) ) OnFailedMove(); return; } 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( Mobile m ) { Run( ( m_Mobile.GetDirectionTo( m ) - 4 ) & Direction.Mask ); } public void OnFailedMove() { if( !m_Mobile.DisallowAllMoves && ( SmartAI ? Utility.Random( 4 ) == 0 : 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 ) ) { if( m_Mobile.Debug ) 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 GetRandomDamage() { return GetRandomDamageSpell(); } public virtual Spell GetRandomDamageSpell() { int maxCircle = (int)( ( myMagery + 20.0 ) / ( 100.0 / 7.0 ) ); if( maxCircle < 1 ) maxCircle = 1; switch( Utility.Random( maxCircle * 2 ) ) { case 0: case 1: return new MagicArrowSpell( m_Mobile, null ); case 2: case 3: return new HarmSpell( m_Mobile, null ); case 4: case 5: return new FireballSpell( m_Mobile, null ); case 6: case 7: return new LightningSpell( m_Mobile, null ); case 8: case 9: return new MindBlastSpell( m_Mobile, null ); case 10: return new EnergyBoltSpell( m_Mobile, null ); case 11: return new ExplosionSpell( m_Mobile, null ); default: return new FlameStrikeSpell( m_Mobile, null ); } } public virtual Spell GetRandomCurse() { return GetRandomCurseSpell(); } public virtual Spell GetRandomCurseSpell() { if( Utility.Random( 4 ) == 3 ) { if( myMagery >= 40.0 ) { 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( myMagery >= 80.0 ) return new ManaVampireSpell( m_Mobile, null ); } return new ManaDrainSpell( m_Mobile, null ); } public virtual Spell DoDispel( Mobile toDispel ) { if( !SmartAI ) { if( ScaleByMagery( DispelChance ) > Utility.RandomDouble() ) return new DispelSpell( m_Mobile, null ); return ChooseSpell( toDispel ); } Spell spell = CheckCastHealingSpell(); if( spell == null ) { if( !m_Mobile.DisallowAllMoves && Utility.Random( (int)m_Mobile.GetDistanceToSqrt( toDispel ) ) == 0 ) spell = new TeleportSpell( m_Mobile, null ); else if( Utility.Random( 3 ) == 0 && !m_Mobile.InRange( toDispel, 3 ) && !toDispel.Paralyzed && !toDispel.Frozen ) spell = new ParalyzeSpell( m_Mobile, null ); else spell = new DispelSpell( m_Mobile, null ); } return spell; } public virtual double myMagery { get { return m_Mobile.Skills[ SkillName.Magery ].Value; } } public virtual Spell ChooseSpell( Mobile c ) { Spell spell = null; if( !SmartAI ) { spell = CheckCastHealingSpell(); if( spell != null ) return spell; switch( Utility.Random( 16 ) ) { case 0: case 1: // Poison them { //m_Mobile.DebugSay( "Attempting to poison" ); if( !c.Poisoned ) spell = new PoisonSpell( m_Mobile, null ); break; } case 2: // Bless ourselves. { //m_Mobile.DebugSay( "Blessing myself" ); spell = new BlessSpell( m_Mobile, null ); break; } case 3: case 4: // Curse them. { //m_Mobile.DebugSay( "Attempting to curse" ); spell = GetRandomCurse(); break; } case 5: // 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 6: // Drain mana { //m_Mobile.DebugSay( "Attempting to drain mana" ); spell = GetRandomManaDrainSpell(); break; } case 7: { //m_Mobile.DebugSay( "Attempting to Invis" ); if( spell == null ) { spell = new InvisibilitySpell( m_Mobile, null ); } break; } default: // Damage them. { //m_Mobile.DebugSay( "Just doing damage" ); spell = GetRandomDamage(); break; } } return spell; } spell = CheckCastHealingSpell(); if( spell != null ) return spell; switch( Utility.Random( 3 ) ) { default: case 0: // Poison them { if( !c.Poisoned ) spell = new PoisonSpell( m_Mobile, null ); break; } case 1: // Deal some damage { spell = GetRandomDamageSpell(); break; } case 2: // Set up a combo { if( m_Mobile.Mana < 40 && m_Mobile.Mana > 15 ) { if( c.Paralyzed && !c.Poisoned ) { 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.Random( 2 ) == 0 && !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; } protected int m_Combo = -1; public virtual Spell DoCombo( Mobile c ) { Spell spell = null; if( m_Combo == 0 ) { spell = new ExplosionSpell( m_Mobile, null ); ++m_Combo; // Move to next spell } else if( m_Combo == 1 ) { spell = new WeakenSpell( m_Mobile, null ); ++m_Combo; // Move to next spell } else if( m_Combo == 2 ) { if( !c.Poisoned ) spell = new PoisonSpell( m_Mobile, null ); ++m_Combo; // Move to next spell } if( m_Combo == 3 && spell == null ) { switch( Utility.Random( 3 ) ) { default: case 0: { if( c.Int < c.Dex ) spell = new FeeblemindSpell( m_Mobile, null ); else spell = new ClumsySpell( m_Mobile, null ); ++m_Combo; // Move to next spell break; } case 1: { spell = new EnergyBoltSpell( m_Mobile, null ); m_Combo = -1; // Reset combo state break; } case 2: { spell = new FlameStrikeSpell( m_Mobile, null ); m_Combo = -1; // Reset combo state break; } } } else if( m_Combo == 4 && spell == null ) { spell = new MindBlastSpell( m_Mobile, null ); m_Combo = -1; } return spell; } private TimeSpan GetDelay() { double del = ScaleByMagery( 3.0 ); double min = 6.0 - ( del * 0.75 ); double max = 6.0 - ( del * 1.25 ); return TimeSpan.FromSeconds( min + ( ( max - min ) * Utility.RandomDouble() ) ); } public override bool DoActionCombat() { Mobile c = m_Mobile.Combatant; m_Mobile.Warmode = true; if( c == null || c.Deleted || !c.Alive || 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 ) ) { if( m_Mobile.Debug ) 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( "Nobody else is around" ); m_Mobile.Combatant = c = m_Mobile.FocusMob; m_Mobile.FocusMob = null; } } if( SmartAI && !m_Mobile.StunReady && m_Mobile.Skills[ SkillName.HandToHand ].Value >= 80.0 && m_Mobile.Skills[ SkillName.Tactics ].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 ) { if( m_Mobile.Hits < m_Mobile.HitsMax * 20 / 100 ) { // We are low on health, should we flee? bool flee = false; if( m_Mobile.Hits < c.Hits ) { // We are more hurt than them int diff = c.Hits - m_Mobile.Hits; flee = ( Utility.Random( 0, 100 ) > ( 10 + diff ) ); // (10 + diff)% chance to flee } else { flee = Utility.Random( 0, 100 ) > 10; // 10% chance to flee } if( flee ) { if( m_Mobile.Debug ) m_Mobile.DebugSay( "I am going to flee from {0}", c.Name ); Action = ActionType.Flee; return true; } } } if( m_Mobile.Spell == null && DateTime.Now > m_NextCastTime && m_Mobile.InRange( c, 12 ) ) { // We are ready to cast a spell Spell spell = null; Mobile 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( SmartAI && m_Combo != -1 ) // We are doing a spell combo { spell = DoCombo( c ); } else if( SmartAI && ( c.Spell is HealSpell || c.Spell is GreaterHealSpell ) && !c.Poisoned ) // They have a heal spell out { spell = new PoisonSpell( m_Mobile, null ); } else { spell = ChooseSpell( c ); } // Now we have a spell picked // Move first before casting if( SmartAI && toDispel != null ) { if( m_Mobile.InRange( toDispel, 10 ) ) RunFrom( toDispel ); else if( !m_Mobile.InRange( toDispel, 12 ) ) RunTo( toDispel ); } else { RunTo( c ); } if( spell != null ) spell.Cast(); TimeSpan delay; if( SmartAI || ( spell is DispelSpell ) ) delay = TimeSpan.FromSeconds( m_Mobile.ActiveSpeed ); else delay = GetDelay(); m_NextCastTime = DateTime.Now; } else if( m_Mobile.Spell == null || !m_Mobile.Spell.IsCasting ) { RunTo( 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.Controlled ) { ProcessTarget(); Spell spell = CheckCastHealingSpell(); if( spell != null ) spell.Cast(); } base.DoActionGuard(); } return true; } public override bool DoActionFlee() { Mobile c = m_Mobile.Combatant; if( ( m_Mobile.Mana > 20 || m_Mobile.Mana == m_Mobile.ManaMax ) && m_Mobile.Hits > ( m_Mobile.HitsMax / 2 ) ) { m_Mobile.DebugSay( "I am stronger now, my guard is up" ); Action = ActionType.Guard; } else if( AcquireFocusMob( m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true ) ) { if( m_Mobile.Debug ) m_Mobile.DebugSay( "I am scared of {0}", m_Mobile.FocusMob.Name ); RunFrom( m_Mobile.FocusMob ); m_Mobile.FocusMob = null; if( m_Mobile.Poisoned && Utility.Random( 0, 5 ) == 0 ) new CureSpell( m_Mobile, null ).Cast(); } else { m_Mobile.DebugSay( "Area seems clear, but my guard is up" ); Action = ActionType.Guard; m_Mobile.Warmode = true; } 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 ) { List aggressed = m_Mobile.Aggressed; List aggressors = m_Mobile.Aggressors; Mobile active = null; double activePrio = 0.0; Mobile comb = m_Mobile.Combatant; 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( int i = 0; i < aggressed.Count; ++i ) { AggressorInfo info = aggressed[ i ]; Mobile m = info.Defender; if( m != comb && m.Combatant == m_Mobile && m_Mobile.InRange( m, 12 ) && CanDispel( m ) ) { double prio = m_Mobile.GetDistanceToSqrt( m ); if( active == null || prio < activePrio ) { active = m; activePrio = prio; if( activePrio <= 2 ) return active; } } } for( int i = 0; i < aggressors.Count; ++i ) { AggressorInfo info = aggressors[ i ]; Mobile m = info.Attacker; if( m != comb && m.Combatant == m_Mobile && m_Mobile.InRange( m, 12 ) && CanDispel( m ) ) { double prio = m_Mobile.GetDistanceToSqrt( m ); if( active == null || prio < activePrio ) { active = m; activePrio = prio; if( activePrio <= 2 ) return active; } } } return active; } else { Map map = m_Mobile.Map; if( map != null ) { Mobile active = null, inactive = null; double actPrio = 0.0, inactPrio = 0.0; Mobile comb = m_Mobile.Combatant; if( comb != null && !comb.Deleted && comb.Alive && !comb.IsDeadBondedPet && CanDispel( comb ) ) { active = inactive = comb; actPrio = inactPrio = m_Mobile.GetDistanceToSqrt( comb ); } foreach( Mobile m in m_Mobile.GetMobilesInRange( 12 ) ) { if( m != m_Mobile && CanDispel( m ) ) { double 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; } } } return active != null ? active : inactive; } } return null; } public bool CanDispel( Mobile m ) { return ( m is BaseCreature && ( (BaseCreature)m ).Summoned && m_Mobile.CanBeHarmful( m, false ) ); } private static int[] m_Offsets = new int[] { -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 bool ProcessTarget() { Target targ = m_Mobile.Target; if( targ == null ) return false; bool isDispel = ( targ is DispelSpell.InternalTarget ); bool isParalyze = ( targ is ParalyzeSpell.InternalTarget ); bool isTeleport = ( targ is TeleportSpell.InternalTarget ); bool isInvisible = ( targ is InvisibilitySpell.InternalTarget ); bool teleportAway = false; Mobile toTarget; if( isInvisible ) { toTarget = m_Mobile; } else if( isDispel ) { toTarget = FindDispelTarget( false ); if( !SmartAI && toTarget != null ) RunTo( toTarget ); else if( toTarget != null && m_Mobile.InRange( toTarget, 10 ) ) RunFrom( toTarget ); } else if( SmartAI && ( isParalyze || isTeleport ) ) { toTarget = FindDispelTarget( true ); if( toTarget == null ) { toTarget = m_Mobile.Combatant; if( toTarget != null ) RunTo( toTarget ); } else if( m_Mobile.InRange( toTarget, 10 ) ) { RunFrom( toTarget ); teleportAway = true; } else { teleportAway = true; } } else { toTarget = m_Mobile.Combatant; 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 ) { Map map = m_Mobile.Map; if( map == null ) { targ.Cancel( m_Mobile, TargetCancelType.Canceled ); return true; } int px, py; if( teleportAway ) { int rx = m_Mobile.X - toTarget.X; int ry = m_Mobile.Y - toTarget.Y; double 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( int i = 0; i < m_Offsets.Length; i += 2 ) { int x = m_Offsets[ i ], y = m_Offsets[ i + 1 ]; Point3D p = new Point3D( px + x, py + y, 0 ); LandTarget 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; } } int teleRange = targ.Range; if( teleRange < 0 ) teleRange = 12; for( int i = 0; i < 10; ++i ) { Point3D randomPoint = new Point3D( m_Mobile.X - teleRange + Utility.Random( teleRange * 2 + 1 ), m_Mobile.Y - teleRange + Utility.Random( teleRange * 2 + 1 ), 0 ); LandTarget 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; } } }