using System; using System.Collections.Generic; using Server.Factions.AI; using Server.Items; using Server.Mobiles; 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.Factions { [Flags] public enum GuardAI { Bless = 0x01, // heal, cure, +stats Curse = 0x02, // poison, -stats Melee = 0x04, // weapons Magic = 0x08, // damage spells Smart = 0x10 // smart weapons/damage spells } public class ComboEntry { public ComboEntry(Type spell, int chance = 100) : this(spell, chance, TimeSpan.Zero) { } public ComboEntry(Type spell, int chance, TimeSpan hold) { Spell = spell; Chance = chance; Hold = hold; } public Type Spell{ get; } public TimeSpan Hold{ get; } public int Chance{ get; } } public class SpellCombo { public static readonly SpellCombo Simple = new SpellCombo(50, new ComboEntry(typeof(ParalyzeSpell), 20), new ComboEntry(typeof(ExplosionSpell), 100, TimeSpan.FromSeconds(2.8)), new ComboEntry(typeof(PoisonSpell), 30), new ComboEntry(typeof(EnergyBoltSpell)) ); public static readonly SpellCombo Strong = new SpellCombo(90, new ComboEntry(typeof(ParalyzeSpell), 20), new ComboEntry(typeof(ExplosionSpell), 50, TimeSpan.FromSeconds(2.8)), new ComboEntry(typeof(PoisonSpell), 30), new ComboEntry(typeof(ExplosionSpell), 100, TimeSpan.FromSeconds(2.8)), new ComboEntry(typeof(EnergyBoltSpell)), new ComboEntry(typeof(PoisonSpell), 30), new ComboEntry(typeof(EnergyBoltSpell)) ); public SpellCombo(int mana, params ComboEntry[] entries) { Mana = mana; Entries = entries; } public int Mana{ get; } public ComboEntry[] Entries{ get; } public static Spell Process(Mobile mob, Mobile targ, ref SpellCombo combo, ref int index, ref DateTime releaseTime) { while (++index < combo.Entries.Length) { ComboEntry entry = combo.Entries[index]; if (entry.Spell == typeof(PoisonSpell) && targ.Poisoned) continue; if (entry.Chance > Utility.Random(100)) { releaseTime = DateTime.UtcNow + entry.Hold; return (Spell)Activator.CreateInstance(entry.Spell, mob, null); } } combo = null; index = -1; return null; } } public class FactionGuardAI : BaseAI { private const int ManaReserve = 30; private BandageContext m_Bandage; private DateTime m_BandageStart; private SpellCombo m_Combo; private int m_ComboIndex = -1; private BaseFactionGuard m_Guard; private DateTime m_ReleaseTarget; public FactionGuardAI(BaseFactionGuard guard) : base(guard) { m_Guard = guard; } public bool IsDamaged => m_Guard.Hits < m_Guard.HitsMax; public bool IsPoisoned => m_Guard.Poisoned; public TimeSpan TimeUntilBandage { get { if (m_Bandage != null && m_Bandage.Timer == null) m_Bandage = null; if (m_Bandage == null) return TimeSpan.MaxValue; TimeSpan ts = m_BandageStart + m_Bandage.Timer.Delay - DateTime.UtcNow; if (ts < TimeSpan.FromSeconds(-1.0)) { m_Bandage = null; return TimeSpan.MaxValue; } if (ts < TimeSpan.Zero) ts = TimeSpan.Zero; return ts; } } public bool IsAllowed(GuardAI flag) { return (m_Guard.GuardAI & flag) == flag; } public bool DequipWeapon() { Container pack = m_Guard.Backpack; if (pack == null) return false; if (m_Guard.Weapon is Item weapon && weapon.Parent == m_Guard && !(weapon is Fists)) { pack.DropItem(weapon); return true; } return false; } public bool EquipWeapon() { Item weapon = m_Guard.Backpack?.FindItemByType(); return weapon != null && m_Guard.EquipItem(weapon); } public bool StartBandage() { m_Bandage = null; if (m_Guard.Backpack?.FindItemByType() == null) return false; m_Bandage = BandageContext.BeginHeal(m_Guard, m_Guard); m_BandageStart = DateTime.UtcNow; return m_Bandage != null; } public bool UseItemByType(Type type) { Container pack = m_Guard.Backpack; Item item = pack?.FindItemByType(type); if (item == null) return false; bool requip = DequipWeapon(); item.OnDoubleClick(m_Guard); if (requip) EquipWeapon(); return true; } public int GetStatMod(Mobile mob, StatType type) { StatMod mod = mob.GetStatMod($"[Magic] {type} Offset"); if (mod == null) return 0; return mod.Offset; } public Spell RandomOffenseSpell() { int maxCircle = (int)((m_Guard.Skills.Magery.Value + 20.0) / (100.0 / 7.0)); if (maxCircle < 1) maxCircle = 1; switch (Utility.Random(maxCircle * 2)) { case 0: case 1: return new MagicArrowSpell(m_Guard); case 2: case 3: return new HarmSpell(m_Guard); case 4: case 5: return new FireballSpell(m_Guard); case 6: case 7: return new LightningSpell(m_Guard); case 8: return new MindBlastSpell(m_Guard); case 9: return new ParalyzeSpell(m_Guard); case 10: return new EnergyBoltSpell(m_Guard); case 11: return new ExplosionSpell(m_Guard); default: return new FlameStrikeSpell(m_Guard); } } 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?.Deleted == false && 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; } 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?.Deleted == false && 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 (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 ?? inactive; } return null; } public bool CanDispel(Mobile m) { return m is BaseCreature creature && creature.Summoned && m_Mobile.CanBeHarmful(creature, false) && !creature.IsAnimatedDead; } 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(Mobile m) { Run((m_Mobile.GetDirectionTo(m) - 4) & Direction.Mask); } public void OnFailedMove() { /*if ( !m_Mobile.DisallowAllMoves && 20 > Utility.Random( 100 ) && IsAllowed( GuardAI.Magic ) ) { 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 override bool Think() { if (m_Mobile.Deleted) return false; Mobile combatant = m_Guard.Combatant; if (combatant?.Deleted != false || !combatant.Alive || combatant.IsDeadBondedPet || !m_Mobile.CanSee(combatant) || !m_Mobile.CanBeHarmful(combatant, false) || combatant.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.Combatant = combatant = m_Mobile.FocusMob; m_Mobile.FocusMob = null; } else { m_Mobile.Combatant = combatant = null; } } if (combatant != null && (!m_Mobile.InLOS(combatant) || !m_Mobile.InRange(combatant, 12))) { if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { m_Mobile.Combatant = combatant = m_Mobile.FocusMob; m_Mobile.FocusMob = null; } else if (!m_Mobile.InRange(combatant, 36)) { m_Mobile.Combatant = combatant = null; } } Mobile dispelTarget = FindDispelTarget(true); if (m_Guard.Target != null && m_ReleaseTarget == DateTime.MinValue) m_ReleaseTarget = DateTime.UtcNow + TimeSpan.FromSeconds(10.0); if (m_Guard.Target != null && DateTime.UtcNow > m_ReleaseTarget) { Target targ = m_Guard.Target; Mobile toHarm = dispelTarget ?? combatant; if ((targ.Flags & TargetFlags.Harmful) != 0 && toHarm != null) { if (m_Guard.Map == toHarm.Map && (targ.Range < 0 || m_Guard.InRange(toHarm, targ.Range)) && m_Guard.CanSee(toHarm) && m_Guard.InLOS(toHarm)) targ.Invoke(m_Guard, toHarm); else if ((targ as ISpellTarget)?.Spell is DispelSpell) targ.Cancel(m_Guard, TargetCancelType.Canceled); } else if ((targ.Flags & TargetFlags.Beneficial) != 0) { targ.Invoke(m_Guard, m_Guard); } else { targ.Cancel(m_Guard, TargetCancelType.Canceled); } m_ReleaseTarget = DateTime.MinValue; } if (dispelTarget != null) { if (Action != ActionType.Combat) Action = ActionType.Combat; m_Guard.Warmode = true; RunFrom(dispelTarget); } else if (combatant != null) { if (Action != ActionType.Combat) Action = ActionType.Combat; m_Guard.Warmode = true; RunTo(combatant); } else if (m_Guard.Orders.Movement != MovementType.Stand) { Mobile toFollow = null; if (m_Guard.Town != null && m_Guard.Orders.Movement == MovementType.Follow) { toFollow = m_Guard.Orders.Follow ?? m_Guard.Town.Sheriff; } if (toFollow != null && toFollow.Map == m_Guard.Map && toFollow.InRange(m_Guard, m_Guard.RangePerception * 3) && Town.FromRegion(toFollow.Region) == m_Guard.Town) { if (Action != ActionType.Combat) Action = ActionType.Combat; if (m_Mobile.CurrentSpeed != m_Mobile.ActiveSpeed) m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; m_Guard.Warmode = true; RunTo(toFollow); } else { if (Action != ActionType.Wander) Action = ActionType.Wander; if (m_Mobile.CurrentSpeed != m_Mobile.PassiveSpeed) m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; m_Guard.Warmode = false; WalkRandomInHome(2, 2, 1); } } else { if (Action != ActionType.Wander) Action = ActionType.Wander; m_Guard.Warmode = false; } if ((IsDamaged || IsPoisoned) && m_Guard.Skills.Healing.Base > 20.0) { TimeSpan ts = TimeUntilBandage; if (ts == TimeSpan.MaxValue) StartBandage(); } Spell spell = m_Mobile.Spell as Spell; if (spell == null && Core.TickCount - m_Mobile.NextSpellTime >= 0) { DateTime toRelease = DateTime.MinValue; if (IsPoisoned) { Poison p = m_Guard.Poison; TimeSpan ts = TimeUntilBandage; if (p != Poison.Lesser || ts == TimeSpan.MaxValue || TimeUntilBandage < TimeSpan.FromSeconds(1.5) || m_Guard.HitsMax - m_Guard.Hits > Utility.Random(250)) { if (IsAllowed(GuardAI.Bless)) spell = new CureSpell(m_Guard); else UseItemByType(typeof(BaseCurePotion)); } } else if (IsDamaged && m_Guard.HitsMax - m_Guard.Hits > Utility.Random(200)) { if (IsAllowed(GuardAI.Magic) && m_Guard.Hits * 100 / Math.Max(m_Guard.HitsMax, 1) < 10 && m_Guard.Home != Point3D.Zero && !Utility.InRange(m_Guard.Location, m_Guard.Home, 15) && m_Guard.Mana >= 11) { spell = new RecallSpell(m_Guard, new RunebookEntry(m_Guard.Home, m_Guard.Map, "Guard's Home")); } else if (IsAllowed(GuardAI.Bless)) { if (m_Guard.Mana >= 11 && m_Guard.Hits + 30 < m_Guard.HitsMax) spell = new GreaterHealSpell(m_Guard); else if (m_Guard.Hits + 10 < m_Guard.HitsMax && (m_Guard.Mana < 11 || m_Guard.NextCombatTime - Core.TickCount > 2000)) spell = new HealSpell(m_Guard); } else if (m_Guard.CanBeginAction()) { UseItemByType(typeof(BaseHealPotion)); } } else if (dispelTarget != null && (IsAllowed(GuardAI.Magic) || IsAllowed(GuardAI.Bless) || IsAllowed(GuardAI.Curse))) { if (!dispelTarget.Paralyzed && m_Guard.Mana > ManaReserve + 20 && 40 > Utility.Random(100)) spell = new ParalyzeSpell(m_Guard); else spell = new DispelSpell(m_Guard); } if (combatant != null) { if (m_Combo != null) { if (spell == null) { spell = SpellCombo.Process(m_Guard, combatant, ref m_Combo, ref m_ComboIndex, ref toRelease); } else { m_Combo = null; m_ComboIndex = -1; } } else if (20 > Utility.Random(100) && IsAllowed(GuardAI.Magic)) { if (80 > Utility.Random(100)) { m_Combo = IsAllowed(GuardAI.Smart) ? SpellCombo.Simple : SpellCombo.Strong; m_ComboIndex = -1; if (m_Guard.Mana >= ManaReserve + m_Combo.Mana) { spell = SpellCombo.Process(m_Guard, combatant, ref m_Combo, ref m_ComboIndex, ref toRelease); } else { m_Combo = null; if (m_Guard.Mana >= ManaReserve + 40) spell = RandomOffenseSpell(); } } else if (m_Guard.Mana >= ManaReserve + 40) { spell = RandomOffenseSpell(); } } if (spell == null && 2 > Utility.Random(100) && m_Guard.Mana >= ManaReserve + 10) { int strMod = GetStatMod(m_Guard, StatType.Str); int dexMod = GetStatMod(m_Guard, StatType.Dex); int intMod = GetStatMod(m_Guard, StatType.Int); List types = new List(); if (strMod <= 0) types.Add(typeof(StrengthSpell)); if (dexMod <= 0 && IsAllowed(GuardAI.Melee)) types.Add(typeof(AgilitySpell)); if (intMod <= 0 && IsAllowed(GuardAI.Magic)) types.Add(typeof(CunningSpell)); if (IsAllowed(GuardAI.Bless)) { if (types.Count > 1) spell = new BlessSpell(m_Guard); else if (types.Count == 1) spell = Activator.CreateInstance(types[0], m_Guard, null) as Spell; } else if (types.Count > 0) { if (types[0] == typeof(StrengthSpell)) UseItemByType(typeof(BaseStrengthPotion)); else if (types[0] == typeof(AgilitySpell)) UseItemByType(typeof(BaseAgilityPotion)); } } if (spell == null && 2 > Utility.Random(100) && m_Guard.Mana >= ManaReserve + 10 && IsAllowed(GuardAI.Curse)) { if (!combatant.Poisoned && 40 > Utility.Random(100)) { spell = new PoisonSpell(m_Guard); } else { int strMod = GetStatMod(combatant, StatType.Str); int dexMod = GetStatMod(combatant, StatType.Dex); int intMod = GetStatMod(combatant, StatType.Int); List types = new List(); if (strMod >= 0) types.Add(typeof(WeakenSpell)); if (dexMod >= 0 && IsAllowed(GuardAI.Melee)) types.Add(typeof(ClumsySpell)); if (intMod >= 0 && IsAllowed(GuardAI.Magic)) types.Add(typeof(FeeblemindSpell)); if (types.Count > 1) spell = new CurseSpell(m_Guard); else if (types.Count == 1) spell = (Spell)Activator.CreateInstance(types[0], m_Guard, null); } } } if (spell != null && m_Guard.HitsMax - m_Guard.Hits + 10 > Utility.Random(100)) { Type type = null; if (spell is GreaterHealSpell) type = typeof(BaseHealPotion); else if (spell is CureSpell) type = typeof(BaseCurePotion); else if (spell is StrengthSpell) type = typeof(BaseStrengthPotion); else if (spell is AgilitySpell) type = typeof(BaseAgilityPotion); if (type == typeof(BaseHealPotion) && !m_Guard.CanBeginAction(type)) type = null; if (type != null && m_Guard.Target == null && UseItemByType(type)) { if (spell is GreaterHealSpell) { if (m_Guard.Hits + 30 > m_Guard.HitsMax && m_Guard.Hits + 10 < m_Guard.HitsMax) spell = new HealSpell(m_Guard); } else { spell = null; } } } else if (spell == null && m_Guard.Stam < m_Guard.StamMax / 3 && IsAllowed(GuardAI.Melee)) { UseItemByType(typeof(BaseRefreshPotion)); } if (spell?.Cast() != true) EquipWeapon(); } else if (spell?.State == SpellState.Sequencing) { EquipWeapon(); } return true; } } }