diff --git a/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs b/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs index 83ff2454b..81bac5bc5 100644 --- a/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs +++ b/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs @@ -411,13 +411,13 @@ namespace Server.Factions { if (m_Mobile.Debug) { - m_Mobile.DebugSay("My move is blocked, so I am going to attack {0}", m_Mobile.FocusMob.Name); + m_Mobile.DebugSay($"My move is blocked, so I am going to attack {m_Mobile.FocusMob.Name}"); } m_Mobile.Combatant = m_Mobile.FocusMob; Action = ActionType.Combat; } - else + else if (m_Mobile.Debug) { m_Mobile.DebugSay("I am stuck"); } @@ -815,17 +815,14 @@ namespace Server.Factions 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 + if (spell is not GreaterHealSpell) { spell = null; } + else if (m_Guard.Hits + 30 > m_Guard.HitsMax && m_Guard.Hits + 10 < m_Guard.HitsMax) + { + spell = new HealSpell(m_Guard); + } } } else if (spell == null && m_Guard.Stam < m_Guard.StamMax / 3 && IsAllowed(GuardAI.Melee)) diff --git a/Projects/UOContent/Mobiles/AI/AIControlMobileTarget.cs b/Projects/UOContent/Mobiles/AI/AIControlMobileTarget.cs index 2ceb4276c..063fc5221 100644 --- a/Projects/UOContent/Mobiles/AI/AIControlMobileTarget.cs +++ b/Projects/UOContent/Mobiles/AI/AIControlMobileTarget.cs @@ -2,43 +2,42 @@ using System.Collections.Generic; using Server.Mobiles; using Server.Targeting; -namespace Server.Targets +namespace Server.Targets; + +public class AIControlMobileTarget : Target { - public class AIControlMobileTarget : Target + private readonly List m_List; + + public AIControlMobileTarget(BaseAI ai, OrderType order) : base( + -1, + false, + order == OrderType.Attack ? TargetFlags.Harmful : TargetFlags.None + ) { - private readonly List m_List; + m_List = new List(); + Order = order; - public AIControlMobileTarget(BaseAI ai, OrderType order) : base( - -1, - false, - order == OrderType.Attack ? TargetFlags.Harmful : TargetFlags.None - ) + AddAI(ai); + } + + public OrderType Order { get; } + + public void AddAI(BaseAI ai) + { + if (!m_List.Contains(ai)) { - m_List = new List(); - Order = order; - - AddAI(ai); + m_List.Add(ai); } + } - public OrderType Order { get; } - - public void AddAI(BaseAI ai) + protected override void OnTarget(Mobile from, object o) + { + if (o is Mobile m) { - if (!m_List.Contains(ai)) + for (var i = 0; i < m_List.Count; ++i) { - m_List.Add(ai); - } - } - - protected override void OnTarget(Mobile from, object o) - { - if (o is Mobile m) - { - for (var i = 0; i < m_List.Count; ++i) - { - m_List[i].EndPickTarget(from, m, Order); - } + m_List[i].EndPickTarget(from, m, Order); } } } -} +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/AnimalAI.cs b/Projects/UOContent/Mobiles/AI/AnimalAI.cs index a339bf04a..73fd6c900 100644 --- a/Projects/UOContent/Mobiles/AI/AnimalAI.cs +++ b/Projects/UOContent/Mobiles/AI/AnimalAI.cs @@ -7,125 +7,140 @@ * */ -namespace Server.Mobiles +namespace Server.Mobiles; + +public class AnimalAI : BaseAI { - public class AnimalAI : BaseAI + public AnimalAI(BaseCreature m) : base(m) { - public AnimalAI(BaseCreature m) : base(m) + } + + public override bool DoActionWander() + { + // New, only flee @ 10% + + var hitPercent = (double)m_Mobile.Hits / m_Mobile.HitsMax; + + if (!m_Mobile.Summoned && !m_Mobile.Controlled && hitPercent < 0.1 && m_Mobile.CanFlee) // Less than 10% health { - } - - public override bool DoActionWander() - { - // New, only flee @ 10% - - var hitPercent = (double)m_Mobile.Hits / m_Mobile.HitsMax; - - if (!m_Mobile.Summoned && !m_Mobile.Controlled && hitPercent < 0.1 && m_Mobile.CanFlee) // Less than 10% health + if (m_Mobile.Debug) { m_Mobile.DebugSay("I am low on health!"); - Action = ActionType.Flee; } - else if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I have detected {0}, attacking", m_Mobile.FocusMob.Name); - } - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else + Action = ActionType.Flee; + } + else if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + { + if (m_Mobile.Debug) { - base.DoActionWander(); + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name}, attacking"); } + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionWander(); + } + + return true; + } + + public override bool DoActionCombat() + { + var combatant = m_Mobile.Combatant; + + if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("My combatant is gone.."); + } + + Action = ActionType.Wander; + return true; } - public override bool DoActionCombat() + if (!WalkMobileRange(combatant, 1, true, m_Mobile.RangeFight, m_Mobile.RangeFight)) { - var combatant = m_Mobile.Combatant; - - if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map) + if (m_Mobile.GetDistanceToSqrt(combatant) > m_Mobile.RangePerception + 1) { - m_Mobile.DebugSay("My combatant is gone.."); + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I cannot find {combatant.Name}"); + } Action = ActionType.Wander; return true; } - if (!WalkMobileRange(combatant, 1, true, m_Mobile.RangeFight, m_Mobile.RangeFight)) + if (m_Mobile.Debug) { - if (m_Mobile.GetDistanceToSqrt(combatant) > m_Mobile.RangePerception + 1) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I cannot find {0}", combatant.Name); - } - - Action = ActionType.Wander; - - return true; - } - - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I should be closer to {0}", combatant.Name); - } + m_Mobile.DebugSay($"I should be closer to {combatant.Name}"); } - - if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee) - { - var hitPercent = (double)m_Mobile.Hits / m_Mobile.HitsMax; - - if (hitPercent < 0.1) - { - m_Mobile.DebugSay("I am low on health!"); - Action = ActionType.Flee; - } - } - - return true; } - public override bool DoActionBackoff() + if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee) { var hitPercent = (double)m_Mobile.Hits / m_Mobile.HitsMax; - if (!m_Mobile.Summoned && !m_Mobile.Controlled && hitPercent < 0.1 && m_Mobile.CanFlee) // Less than 10% health + if (hitPercent <= 0.1) { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I am low on health!"); + } + Action = ActionType.Flee; } - else + } + + return true; + } + + public override bool DoActionBackoff() + { + var hitPercent = (double)m_Mobile.Hits / m_Mobile.HitsMax; + + if (!m_Mobile.Summoned && !m_Mobile.Controlled && hitPercent < 0.1 && m_Mobile.CanFlee) // Less than 10% health + { + Action = ActionType.Flee; + } + else if (AcquireFocusMob(m_Mobile.RangePerception * 2, FightMode.Closest, true, false, true)) + { + if (WalkMobileRange(m_Mobile.FocusMob, 1, false, m_Mobile.RangePerception, m_Mobile.RangePerception * 2)) { - if (AcquireFocusMob(m_Mobile.RangePerception * 2, FightMode.Closest, true, false, true)) + if (m_Mobile.Debug) { - if (WalkMobileRange(m_Mobile.FocusMob, 1, false, m_Mobile.RangePerception, m_Mobile.RangePerception * 2)) - { - m_Mobile.DebugSay("Well, here I am safe"); - Action = ActionType.Wander; - } - } - else - { - m_Mobile.DebugSay("I have lost my focus, lets relax"); - Action = ActionType.Wander; + m_Mobile.DebugSay("Well, here I am safe"); } + + Action = ActionType.Wander; + } + } + else + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I have lost my focus, lets relax"); } - return true; + Action = ActionType.Wander; } - public override bool DoActionFlee() - { - AcquireFocusMob(m_Mobile.RangePerception * 2, m_Mobile.FightMode, true, false, true); + return true; + } - m_Mobile.FocusMob ??= m_Mobile.Combatant; + public override bool DoActionFlee() + { + AcquireFocusMob(m_Mobile.RangePerception * 2, m_Mobile.FightMode, true, false, true); - return base.DoActionFlee(); - } + m_Mobile.FocusMob ??= m_Mobile.Combatant; + + return base.DoActionFlee(); } } diff --git a/Projects/UOContent/Mobiles/AI/ArcherAI.cs b/Projects/UOContent/Mobiles/AI/ArcherAI.cs index 75b9a7058..01faf9f5c 100644 --- a/Projects/UOContent/Mobiles/AI/ArcherAI.cs +++ b/Projects/UOContent/Mobiles/AI/ArcherAI.cs @@ -1,135 +1,126 @@ +using System; using Server.Items; -namespace Server.Mobiles -{ - public class ArcherAI : BaseAI - { - public ArcherAI(BaseCreature m) : base(m) - { - } +namespace Server.Mobiles; - public override bool DoActionWander() +public class ArcherAI : BaseAI +{ + public ArcherAI(BaseCreature m) : base(m) + { + } + + public override bool DoActionWander() + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have no combatant"); + } - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + { + if (m_Mobile.Debug) { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I have detected {0} and I will attack", m_Mobile.FocusMob.Name); - } - - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else - { - return base.DoActionWander(); + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name} and I will attack"); } + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + return base.DoActionWander(); + } + + return true; + } + + public override bool DoActionCombat() + { + if (m_Mobile.Combatant?.Deleted != false || !m_Mobile.Combatant.Alive || + m_Mobile.Combatant.IsDeadBondedPet) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("My combatant is deleted"); + } + + Action = ActionType.Guard; return true; } - public override bool DoActionCombat() + if (Core.TickCount - m_Mobile.LastMoveTime > 1000) { - if (m_Mobile.Combatant?.Deleted != false || !m_Mobile.Combatant.Alive || - m_Mobile.Combatant.IsDeadBondedPet) - { - m_Mobile.DebugSay("My combatant is deleted"); - Action = ActionType.Guard; - return true; - } - - if (Core.TickCount - m_Mobile.LastMoveTime > 1000) - { - if ( - m_Mobile.Combatant != null && - !WalkMobileRange( - m_Mobile.Combatant, - 1, - true, - m_Mobile.RangeFight, - m_Mobile.Weapon.MaxRange - ) + if ( + m_Mobile.Combatant != null && + !WalkMobileRange( + m_Mobile.Combatant, + 1, + true, + m_Mobile.RangeFight, + m_Mobile.Weapon.MaxRange ) + ) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I am still not in range of {m_Mobile.Combatant.Name}"); + } + + if ((int)m_Mobile.GetDistanceToSqrt(m_Mobile.Combatant) > m_Mobile.RangePerception + 1) { if (m_Mobile.Debug) { - m_Mobile.DebugSay("I am still not in range of {0}", m_Mobile.Combatant.Name); + m_Mobile.DebugSay($"I have lost {m_Mobile.Combatant.Name}"); } - if ((int)m_Mobile.GetDistanceToSqrt(m_Mobile.Combatant) > m_Mobile.RangePerception + 1) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I have lost {0}", m_Mobile.Combatant.Name); - } - - m_Mobile.Combatant = null; - Action = ActionType.Guard; - return true; - } + m_Mobile.Combatant = null; + Action = ActionType.Guard; + return true; } } + } - // When we have no ammo, we flee - var pack = m_Mobile.Backpack; + // When we have no ammo, we flee + var pack = m_Mobile.Backpack; - if (pack?.FindItemByType() == null) + if (pack?.FindItemByType() == null) + { + Action = ActionType.Flee; + return true; + } + + // At 20% we should check if we must leave + if (m_Mobile.Combatant != null && m_Mobile.Hits < m_Mobile.HitsMax * 20 / 100 && m_Mobile.CanFlee) + { + // 10% to flee + the diff of hits + var fleeChance = 10 + Math.Max(0, m_Mobile.Combatant.Hits - m_Mobile.Hits); + + if (Utility.Random(0, 100) > fleeChance) { Action = ActionType.Flee; - return true; } - - // At 20% we should check if we must leave - if (m_Mobile.Hits < m_Mobile.HitsMax * 20 / 100 && m_Mobile.CanFlee) - { - var bFlee = false; - // if my current hits are more than my opponent, i don't care - if (m_Mobile.Combatant != null && m_Mobile.Hits < m_Mobile.Combatant.Hits) - { - var iDiff = m_Mobile.Combatant.Hits - m_Mobile.Hits; - - if (Utility.Random(0, 100) > 10 + iDiff) // 10% to flee + the diff of hits - { - bFlee = true; - } - } - else if (m_Mobile.Combatant != null && m_Mobile.Hits >= m_Mobile.Combatant.Hits) - { - if (Utility.Random(0, 100) > 10) // 10% to flee - { - bFlee = true; - } - } - - if (bFlee) - { - Action = ActionType.Flee; - } - } - - return true; } - public override bool DoActionGuard() - { - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I have detected {0}, attacking", m_Mobile.FocusMob.Name); - } - - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else - { - base.DoActionGuard(); - } - - return true; - } + return true; } -} + + public override bool DoActionGuard() + { + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name}, attacking"); + } + + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionGuard(); + } + + return true; + } +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index d6d735d57..77312eb8c 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -13,1226 +13,1412 @@ using Server.Spells.Spellweaving; using Server.Targets; using MoveImpl = Server.Movement.MovementImpl; -namespace Server.Mobiles +namespace Server.Mobiles; + +public enum AIType { - public enum AIType + AI_Use_Default, + AI_Melee, + AI_Animal, + AI_Archer, + AI_Healer, + AI_Vendor, + AI_Mage, + AI_Berserk, + AI_Predator, + AI_Thief +} + +public enum ActionType +{ + Wander, + Combat, + Guard, + Flee, + Backoff, + Interact +} + +public abstract class BaseAI +{ + private static readonly SkillName[] m_KeywordTable = { - AI_Use_Default, - AI_Melee, - AI_Animal, - AI_Archer, - AI_Healer, - AI_Vendor, - AI_Mage, - AI_Berserk, - AI_Predator, - AI_Thief + SkillName.Parry, + SkillName.Healing, + SkillName.Hiding, + SkillName.Stealing, + SkillName.Alchemy, + SkillName.AnimalLore, + SkillName.ItemID, + SkillName.ArmsLore, + SkillName.Begging, + SkillName.Blacksmith, + SkillName.Fletching, + SkillName.Peacemaking, + SkillName.Camping, + SkillName.Carpentry, + SkillName.Cartography, + SkillName.Cooking, + SkillName.DetectHidden, + SkillName.Discordance, // ?? + SkillName.EvalInt, + SkillName.Fishing, + SkillName.Provocation, + SkillName.Lockpicking, + SkillName.Magery, + SkillName.MagicResist, + SkillName.Tactics, + SkillName.Snooping, + SkillName.RemoveTrap, + SkillName.Musicianship, + SkillName.Poisoning, + SkillName.Archery, + SkillName.SpiritSpeak, + SkillName.Tailoring, + SkillName.AnimalTaming, + SkillName.TasteID, + SkillName.Tinkering, + SkillName.Veterinary, + SkillName.Forensics, + SkillName.Herding, + SkillName.Tracking, + SkillName.Stealth, + SkillName.Inscribe, + SkillName.Swords, + SkillName.Macing, + SkillName.Fencing, + SkillName.Wrestling, + SkillName.Lumberjacking, + SkillName.Mining, + SkillName.Meditation + }; + + private static readonly Queue m_Obstacles = new(); + protected ActionType m_Action; + + public BaseCreature m_Mobile; + + private long m_NextDetectHidden; + private long m_NextStopGuard; + + protected PathFollower m_Path; + public Timer m_Timer; + + public BaseAI(BaseCreature m) + { + m_Mobile = m; + + m_Timer = new AITimer(this); + + bool activate; + + if (!m.PlayerRangeSensitive) + { + activate = true; + } + else if (World.Loading) + { + activate = false; + } + else if (m.Map == null || m.Map == Map.Internal || !m.Map.GetSector(m.Location).Active) + { + activate = false; + } + else + { + activate = true; + } + + if (activate) + { + m_Timer.Start(); + } + + Action = ActionType.Wander; } - public enum ActionType + public ActionType Action { - Wander, - Combat, - Guard, - Flee, - Backoff, - Interact + get => m_Action; + set + { + m_Action = value; + OnActionChanged(); + } } - public abstract class BaseAI + public long NextMove { get; set; } + + public virtual bool CanDetectHidden => m_Mobile.Skills.DetectHidden.Value > 0; + + public virtual bool WasNamed(string speech) { - private static readonly SkillName[] m_KeywordTable = + var name = m_Mobile.Name; + + return name != null && speech.InsensitiveStartsWith(name); + } + + public virtual void GetContextMenuEntries(Mobile from, List list) + { + if (from.Alive && m_Mobile.Controlled && from.InRange(m_Mobile, 14)) { - SkillName.Parry, - SkillName.Healing, - SkillName.Hiding, - SkillName.Stealing, - SkillName.Alchemy, - SkillName.AnimalLore, - SkillName.ItemID, - SkillName.ArmsLore, - SkillName.Begging, - SkillName.Blacksmith, - SkillName.Fletching, - SkillName.Peacemaking, - SkillName.Camping, - SkillName.Carpentry, - SkillName.Cartography, - SkillName.Cooking, - SkillName.DetectHidden, - SkillName.Discordance, // ?? - SkillName.EvalInt, - SkillName.Fishing, - SkillName.Provocation, - SkillName.Lockpicking, - SkillName.Magery, - SkillName.MagicResist, - SkillName.Tactics, - SkillName.Snooping, - SkillName.RemoveTrap, - SkillName.Musicianship, - SkillName.Poisoning, - SkillName.Archery, - SkillName.SpiritSpeak, - SkillName.Tailoring, - SkillName.AnimalTaming, - SkillName.TasteID, - SkillName.Tinkering, - SkillName.Veterinary, - SkillName.Forensics, - SkillName.Herding, - SkillName.Tracking, - SkillName.Stealth, - SkillName.Inscribe, - SkillName.Swords, - SkillName.Macing, - SkillName.Fencing, - SkillName.Wrestling, - SkillName.Lumberjacking, - SkillName.Mining, - SkillName.Meditation - }; + if (from == m_Mobile.ControlMaster) + { + list.Add(new InternalEntry(from, 6107, 14, m_Mobile, this, OrderType.Guard)); // Command: Guard + list.Add(new InternalEntry(from, 6108, 14, m_Mobile, this, OrderType.Follow)); // Command: Follow - private static readonly Queue m_Obstacles = new(); - protected ActionType m_Action; + if (m_Mobile.CanDrop) + { + list.Add(new InternalEntry(from, 6109, 14, m_Mobile, this, OrderType.Drop)); // Command: Drop + } - public BaseCreature m_Mobile; + list.Add(new InternalEntry(from, 6111, 14, m_Mobile, this, OrderType.Attack)); // Command: Kill - private long m_NextDetectHidden; - private long m_NextStopGuard; + list.Add(new InternalEntry(from, 6112, 14, m_Mobile, this, OrderType.Stop)); // Command: Stop + list.Add(new InternalEntry(from, 6114, 14, m_Mobile, this, OrderType.Stay)); // Command: Stay - protected PathFollower m_Path; - public Timer m_Timer; + if (!m_Mobile.Summoned && m_Mobile is not GrizzledMare) + { + list.Add(new InternalEntry(from, 6110, 14, m_Mobile, this, OrderType.Friend)); // Add Friend + list.Add(new InternalEntry(from, 6099, 14, m_Mobile, this, OrderType.Unfriend)); // Remove Friend + list.Add(new InternalEntry(from, 6113, 14, m_Mobile, this, OrderType.Transfer)); // Transfer + } - public BaseAI(BaseCreature m) + list.Add(new InternalEntry(from, 6118, 14, m_Mobile, this, OrderType.Release)); // Release + } + else if (m_Mobile.IsPetFriend(from)) + { + list.Add(new InternalEntry(from, 6108, 14, m_Mobile, this, OrderType.Follow)); // Command: Follow + list.Add(new InternalEntry(from, 6112, 14, m_Mobile, this, OrderType.Stop)); // Command: Stop + list.Add(new InternalEntry(from, 6114, 14, m_Mobile, this, OrderType.Stay)); // Command: Stay + } + } + } + + public virtual void BeginPickTarget(Mobile from, OrderType order) + { + if (m_Mobile.Deleted || !m_Mobile.Controlled || !from.InRange(m_Mobile, 14) || from.Map != m_Mobile.Map) { - m_Mobile = m; + return; + } - m_Timer = new AITimer(this); + var isOwner = from == m_Mobile.ControlMaster; + var isFriend = !isOwner && m_Mobile.IsPetFriend(from); - bool activate; + if (!isOwner && !isFriend) + { + return; + } - if (!m.PlayerRangeSensitive) + if (isFriend && order != OrderType.Follow && order != OrderType.Stay && order != OrderType.Stop) + { + return; + } + + if (from.Target == null) + { + if (order == OrderType.Transfer) { - activate = true; + from.SendLocalizedMessage(502038); // Click on the person to transfer ownership to. } - else if (World.Loading) + else if (order == OrderType.Friend) { - activate = false; + from.SendLocalizedMessage(502020); // Click on the player whom you wish to make a co-owner. } - else if (m.Map == null || m.Map == Map.Internal || !m.Map.GetSector(m.Location).Active) + else if (order == OrderType.Unfriend) { - activate = false; + from.SendLocalizedMessage(1070948); // Click on the player whom you wish to remove as a co-owner. + } + + from.Target = new AIControlMobileTarget(this, order); + } + else if (from.Target is AIControlMobileTarget t) + { + if (t.Order == order) + { + t.AddAI(this); + } + } + } + + public virtual void OnAggressiveAction(Mobile aggressor) + { + var currentCombat = m_Mobile.Combatant; + + if (currentCombat != null && !aggressor.Hidden && currentCombat != aggressor && + m_Mobile.GetDistanceToSqrt(currentCombat) > m_Mobile.GetDistanceToSqrt(aggressor)) + { + m_Mobile.Combatant = aggressor; + } + } + + public virtual void EndPickTarget(Mobile from, Mobile target, OrderType order) + { + if (m_Mobile.Deleted || !m_Mobile.Controlled || !from.InRange(m_Mobile, 14) || from.Map != m_Mobile.Map || + !from.CheckAlive()) + { + return; + } + + var isOwner = from == m_Mobile.ControlMaster; + var isFriend = !isOwner && m_Mobile.IsPetFriend(from); + + if (!isOwner && !isFriend) + { + return; + } + + if (isFriend && order != OrderType.Follow && order != OrderType.Stay && order != OrderType.Stop) + { + return; + } + + if (order == OrderType.Attack) + { + if (target is BaseCreature creature && creature.IsScaryToPets && m_Mobile.IsScaredOfScaryThings) + { + m_Mobile.SayTo(from, "Your pet refuses to attack this creature!"); + return; + } + + if (SolenHelper.CheckRedFriendship(from) && + target is RedSolenInfiltratorQueen or RedSolenInfiltratorWarrior or RedSolenQueen or RedSolenWarrior or RedSolenWorker + || SolenHelper.CheckBlackFriendship(from) && + target is BlackSolenInfiltratorQueen or BlackSolenInfiltratorWarrior or BlackSolenQueen or BlackSolenWarrior or BlackSolenWorker) + { + from.SendAsciiMessage("You can not force your pet to attack a creature you are protected from."); + return; + } + + if (target is BaseFactionGuard) + { + m_Mobile.SayTo(from, "Your pet refuses to attack the guard."); + return; + } + } + + if (m_Mobile.CheckControlChance(from)) + { + m_Mobile.ControlTarget = target; + m_Mobile.ControlOrder = order; + } + } + + public virtual bool HandlesOnSpeech(Mobile from) + { + if (from.AccessLevel >= AccessLevel.GameMaster) + { + return true; + } + + if (from.Alive && m_Mobile.Controlled && m_Mobile.Commandable && + (from == m_Mobile.ControlMaster || m_Mobile.IsPetFriend(from))) + { + return true; + } + + return from.Alive && from.InRange(m_Mobile.Location, 3) && m_Mobile.IsHumanInTown(); + } + + public virtual void OnSpeech(SpeechEventArgs e) + { + if (e.Mobile.Alive && e.Mobile.InRange(m_Mobile.Location, 3) && m_Mobile.IsHumanInTown()) + { + if (e.HasKeyword(0x9D) && WasNamed(e.Speech)) // *move* + { + if (m_Mobile.Combatant != null) + { + // I am too busy fighting to deal with thee! + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); + } + else + { + // Excuse me? + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501516); + WalkRandomInHome(2, 2, 1); + } + } + else if (e.HasKeyword(0x9E) && WasNamed(e.Speech)) // *time* + { + if (m_Mobile.Combatant != null) + { + // I am too busy fighting to deal with thee! + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); + } + else + { + Clock.GetTime(m_Mobile, out var generalNumber, out _); + + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, generalNumber); + } + } + else if (e.HasKeyword(0x6C) && WasNamed(e.Speech)) // *train + { + if (m_Mobile.Combatant != null) + { + // I am too busy fighting to deal with thee! + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); + } + else + { + var foundSomething = false; + + var ourSkills = m_Mobile.Skills; + var theirSkills = e.Mobile.Skills; + + for (var i = 0; i < ourSkills.Length && i < theirSkills.Length; ++i) + { + var skill = ourSkills[i]; + var theirSkill = theirSkills[i]; + + if (skill?.Base >= 60.0 && m_Mobile.CheckTeach(skill.SkillName, e.Mobile)) + { + var toTeach = skill.Base / 3.0; + + if (toTeach > 42.0) + { + toTeach = 42.0; + } + + if (toTeach > theirSkill.Base) + { + var number = 1043059 + i; + + if (number > 1043107) + { + continue; + } + + if (!foundSomething) + { + m_Mobile.Say(1043058); // I can train the following: + } + + m_Mobile.Say(number); + + foundSomething = true; + } + } + } + + if (!foundSomething) + { + m_Mobile.Say(501505); // Alas, I cannot teach thee anything. + } + } } else { - activate = true; + var toTrain = (SkillName)(-1); + + for (var i = 0; toTrain == (SkillName)(-1) && i < e.Keywords.Length; ++i) + { + var keyword = e.Keywords[i]; + + if (keyword == 0x154) + { + toTrain = SkillName.Anatomy; + } + else if (keyword >= 0x6D && keyword <= 0x9C) + { + var index = keyword - 0x6D; + + if (index >= 0 && index < m_KeywordTable.Length) + { + toTrain = m_KeywordTable[index]; + } + } + } + + if (toTrain != (SkillName)(-1) && WasNamed(e.Speech)) + { + if (m_Mobile.Combatant != null) + { + // I am too busy fighting to deal with thee! + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); + } + else + { + var skills = m_Mobile.Skills; + var skill = skills[toTrain]; + + if (skill == null || skill.Base < 60.0 || !m_Mobile.CheckTeach(toTrain, e.Mobile)) + { + m_Mobile.Say(501507); // 'Tis not something I can teach thee of. + } + else + { + m_Mobile.Teach(toTrain, e.Mobile, 0, false); + } + } + } + } + } + + if (m_Mobile.Controlled && m_Mobile.Commandable) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Listening..."); } - if (activate) + var isOwner = e.Mobile == m_Mobile.ControlMaster; + var isFriend = !isOwner && m_Mobile.IsPetFriend(e.Mobile); + + if (e.Mobile.Alive && (isOwner || isFriend)) { - m_Timer.Start(); + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("It's from my master"); + } + + var keywords = e.Keywords; + var speech = e.Speech; + + // First, check the all* + for (var i = 0; i < keywords.Length; ++i) + { + var keyword = keywords[i]; + + switch (keyword) + { + case 0x164: // all come + { + if (!isOwner) + { + break; + } + + if (m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Come; + } + + return; + } + case 0x165: // all follow + { + BeginPickTarget(e.Mobile, OrderType.Follow); + return; + } + case 0x166: // all guard + case 0x16B: // all guard me + { + if (!isOwner) + { + break; + } + + if (m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Guard; + } + + return; + } + case 0x167: // all stop + { + if (m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Stop; + } + + return; + } + case 0x168: // all kill + case 0x169: // all attack + { + if (!isOwner) + { + break; + } + + BeginPickTarget(e.Mobile, OrderType.Attack); + return; + } + case 0x16C: // all follow me + { + if (m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = e.Mobile; + m_Mobile.ControlOrder = OrderType.Follow; + } + + return; + } + case 0x170: // all stay + { + if (m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Stay; + } + + return; + } + } + } + + // No all*, so check *command + for (var i = 0; i < keywords.Length; ++i) + { + var keyword = keywords[i]; + + switch (keyword) + { + case 0x155: // *come + { + if (!isOwner) + { + break; + } + + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Come; + } + + return; + } + case 0x156: // *drop + { + if (!isOwner) + { + break; + } + + if (!m_Mobile.IsDeadPet && !m_Mobile.Summoned && WasNamed(speech) && + m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Drop; + } + + return; + } + case 0x15A: // *follow + { + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + BeginPickTarget(e.Mobile, OrderType.Follow); + } + + return; + } + case 0x15B: // *friend + { + if (!isOwner) + { + break; + } + + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + if (m_Mobile.Summoned || m_Mobile is GrizzledMare) + { + e.Mobile.SendLocalizedMessage( + 1005481 + ); // Summoned creatures are loyal only to their summoners. + } + else if (e.Mobile.HasTrade) + { + e.Mobile.SendLocalizedMessage( + 1070947 + ); // You cannot friend a pet with a trade pending + } + else + { + BeginPickTarget(e.Mobile, OrderType.Friend); + } + } + + return; + } + case 0x15C: // *guard + { + if (!isOwner) + { + break; + } + + if (!m_Mobile.IsDeadPet && WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Guard; + } + + return; + } + case 0x15D: // *kill + case 0x15E: // *attack + { + if (!isOwner) + { + break; + } + + if (!m_Mobile.IsDeadPet && WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + BeginPickTarget(e.Mobile, OrderType.Attack); + } + + return; + } + case 0x15F: // *patrol + { + if (!isOwner) + { + break; + } + + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Patrol; + } + + return; + } + case 0x161: // *stop + { + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Stop; + } + + return; + } + case 0x163: // *follow me + { + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = e.Mobile; + m_Mobile.ControlOrder = OrderType.Follow; + } + + return; + } + case 0x16D: // *release + { + if (!isOwner) + { + break; + } + + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + if (!m_Mobile.Summoned) + { + e.Mobile.SendGump(new ConfirmReleaseGump(e.Mobile, m_Mobile)); + } + else + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Release; + } + } + + return; + } + case 0x16E: // *transfer + { + if (!isOwner) + { + break; + } + + if (!m_Mobile.IsDeadPet && WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + if (m_Mobile.Summoned || m_Mobile is GrizzledMare) + { + e.Mobile.SendLocalizedMessage( + 1005487 + ); // You cannot transfer ownership of a summoned creature. + } + else if (e.Mobile.HasTrade) + { + e.Mobile.SendLocalizedMessage( + 1010507 + ); // You cannot transfer a pet with a trade pending + } + else + { + BeginPickTarget(e.Mobile, OrderType.Transfer); + } + } + + return; + } + case 0x16F: // *stay + { + if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) + { + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Stay; + } + + return; + } + } + } + } + } + else + { + if (e.Mobile.AccessLevel >= AccessLevel.GameMaster) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("It's from a GM"); + } + + if (m_Mobile.FindMyName(e.Speech, true)) + { + var str = e.Speech.Split(' '); + int i; + + for (i = 0; i < str.Length; i++) + { + var word = str[i]; + + if (word.InsensitiveEquals("obey")) + { + m_Mobile.SetControlMaster(e.Mobile); + + if (m_Mobile.Summoned) + { + m_Mobile.SummonMaster = e.Mobile; + } + + return; + } + } + } + } + } + } + + public virtual bool Think() + { + if (m_Mobile.Deleted) + { + return false; + } + + if (CheckFlee()) + { + return true; + } + + switch (Action) + { + case ActionType.Wander: + { + m_Mobile.OnActionWander(); + return DoActionWander(); + } + + case ActionType.Combat: + { + m_Mobile.OnActionCombat(); + return DoActionCombat(); + } + + case ActionType.Guard: + { + m_Mobile.OnActionGuard(); + return DoActionGuard(); + } + + case ActionType.Flee: + { + m_Mobile.OnActionFlee(); + return DoActionFlee(); + } + + case ActionType.Interact: + { + m_Mobile.OnActionInteract(); + return DoActionInteract(); + } + + case ActionType.Backoff: + { + m_Mobile.OnActionBackoff(); + return DoActionBackoff(); + } + + default: + { + return false; + } + } + } + + public virtual void OnActionChanged() + { + switch (Action) + { + case ActionType.Wander: + { + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + m_Mobile.FocusMob = null; + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + break; + } + + case ActionType.Combat: + { + m_Mobile.Warmode = true; + m_Mobile.FocusMob = null; + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + break; + } + + case ActionType.Guard: + { + m_Mobile.Warmode = true; + m_Mobile.FocusMob = null; + m_Mobile.Combatant = null; + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + m_NextStopGuard = Core.TickCount + (int)TimeSpan.FromSeconds(10).TotalMilliseconds; + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + break; + } + + case ActionType.Flee: + { + m_Mobile.Warmode = true; + m_Mobile.FocusMob = null; + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + break; + } + + case ActionType.Interact: + { + m_Mobile.Warmode = false; + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + break; + } + + case ActionType.Backoff: + { + m_Mobile.Warmode = false; + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + break; + } + } + } + + public virtual bool OnAtWayPoint() => true; + + public virtual bool DoActionWander() + { + if (CheckHerding()) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Praise the shepherd!"); + } + } + else if (m_Mobile.CurrentWayPoint != null) + { + var point = m_Mobile.CurrentWayPoint; + if ((point.X != m_Mobile.Location.X || point.Y != m_Mobile.Location.Y) && point.Map == m_Mobile.Map && + point.Parent == null && !point.Deleted) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I will move towards my waypoint."); + } + + DoMove(m_Mobile.GetDirectionTo(m_Mobile.CurrentWayPoint)); + } + else if (OnAtWayPoint()) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I will go to the next waypoint"); + } + + m_Mobile.CurrentWayPoint = point.NextPoint; + if (point.NextPoint?.Deleted == true) + { + m_Mobile.CurrentWayPoint = point.NextPoint = point.NextPoint.NextPoint; + } + } + } + else if (m_Mobile.IsAnimatedDead) + { + // animated dead follow their master + var master = m_Mobile.SummonMaster; + + if (master != null && master.Map == m_Mobile.Map && master.InRange(m_Mobile, m_Mobile.RangePerception)) + { + MoveTo(master, false, 1); + } + else + { + WalkRandomInHome(2, 2, 1); + } + } + else if (CheckMove()) + { + if (!m_Mobile.CheckIdle()) + { + WalkRandomInHome(2, 2, 1); + } + } + + if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && + !m_Mobile.Combatant.IsDeadBondedPet) + { + m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant); + } + + return true; + } + + public virtual bool DoActionCombat() + { + if (Core.AOS && CheckHerding()) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Praise the shepherd!"); + } + } + else + { + var c = m_Mobile.Combatant; + + if (c?.Deleted != false || c.Map != m_Mobile.Map || !c.Alive || c.IsDeadBondedPet) + { + Action = ActionType.Wander; + } + else + { + m_Mobile.Direction = m_Mobile.GetDirectionTo(c); + } + } + + return true; + } + + public virtual bool DoActionGuard() + { + if (Core.AOS && CheckHerding()) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Praise the shepherd!"); + } + } + else if (Core.TickCount - m_NextStopGuard < 0) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I am on guard"); + } + // m_Mobile.Turn( Utility.Random(0, 2) - 1 ); + } + else + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I stopped being on guard"); } Action = ActionType.Wander; } - public ActionType Action + return true; + } + + public virtual bool DoActionFlee() + { + var from = m_Mobile.FocusMob; + + if (from?.Deleted != false || from.Map != m_Mobile.Map) { - get => m_Action; - set - { - m_Action = value; - OnActionChanged(); - } - } - - public long NextMove { get; set; } - - public virtual bool CanDetectHidden => m_Mobile.Skills.DetectHidden.Value > 0; - - public virtual bool WasNamed(string speech) - { - var name = m_Mobile.Name; - - return name != null && speech.InsensitiveStartsWith(name); - } - - public virtual void GetContextMenuEntries(Mobile from, List list) - { - if (from.Alive && m_Mobile.Controlled && from.InRange(m_Mobile, 14)) - { - if (from == m_Mobile.ControlMaster) - { - list.Add(new InternalEntry(from, 6107, 14, m_Mobile, this, OrderType.Guard)); // Command: Guard - list.Add(new InternalEntry(from, 6108, 14, m_Mobile, this, OrderType.Follow)); // Command: Follow - - if (m_Mobile.CanDrop) - { - list.Add(new InternalEntry(from, 6109, 14, m_Mobile, this, OrderType.Drop)); // Command: Drop - } - - list.Add(new InternalEntry(from, 6111, 14, m_Mobile, this, OrderType.Attack)); // Command: Kill - - list.Add(new InternalEntry(from, 6112, 14, m_Mobile, this, OrderType.Stop)); // Command: Stop - list.Add(new InternalEntry(from, 6114, 14, m_Mobile, this, OrderType.Stay)); // Command: Stay - - if (!m_Mobile.Summoned && m_Mobile is not GrizzledMare) - { - list.Add(new InternalEntry(from, 6110, 14, m_Mobile, this, OrderType.Friend)); // Add Friend - list.Add(new InternalEntry(from, 6099, 14, m_Mobile, this, OrderType.Unfriend)); // Remove Friend - list.Add(new InternalEntry(from, 6113, 14, m_Mobile, this, OrderType.Transfer)); // Transfer - } - - list.Add(new InternalEntry(from, 6118, 14, m_Mobile, this, OrderType.Release)); // Release - } - else if (m_Mobile.IsPetFriend(from)) - { - list.Add(new InternalEntry(from, 6108, 14, m_Mobile, this, OrderType.Follow)); // Command: Follow - list.Add(new InternalEntry(from, 6112, 14, m_Mobile, this, OrderType.Stop)); // Command: Stop - list.Add(new InternalEntry(from, 6114, 14, m_Mobile, this, OrderType.Stay)); // Command: Stay - } - } - } - - public virtual void BeginPickTarget(Mobile from, OrderType order) - { - if (m_Mobile.Deleted || !m_Mobile.Controlled || !from.InRange(m_Mobile, 14) || from.Map != m_Mobile.Map) - { - return; - } - - var isOwner = from == m_Mobile.ControlMaster; - var isFriend = !isOwner && m_Mobile.IsPetFriend(from); - - if (!isOwner && !isFriend) - { - return; - } - - if (isFriend && order != OrderType.Follow && order != OrderType.Stay && order != OrderType.Stop) - { - return; - } - - if (from.Target == null) - { - if (order == OrderType.Transfer) - { - from.SendLocalizedMessage(502038); // Click on the person to transfer ownership to. - } - else if (order == OrderType.Friend) - { - from.SendLocalizedMessage(502020); // Click on the player whom you wish to make a co-owner. - } - else if (order == OrderType.Unfriend) - { - from.SendLocalizedMessage(1070948); // Click on the player whom you wish to remove as a co-owner. - } - - from.Target = new AIControlMobileTarget(this, order); - } - else if (from.Target is AIControlMobileTarget t) - { - if (t.Order == order) - { - t.AddAI(this); - } - } - } - - public virtual void OnAggressiveAction(Mobile aggressor) - { - var currentCombat = m_Mobile.Combatant; - - if (currentCombat != null && !aggressor.Hidden && currentCombat != aggressor && - m_Mobile.GetDistanceToSqrt(currentCombat) > m_Mobile.GetDistanceToSqrt(aggressor)) - { - m_Mobile.Combatant = aggressor; - } - } - - public virtual void EndPickTarget(Mobile from, Mobile target, OrderType order) - { - if (m_Mobile.Deleted || !m_Mobile.Controlled || !from.InRange(m_Mobile, 14) || from.Map != m_Mobile.Map || - !from.CheckAlive()) - { - return; - } - - var isOwner = from == m_Mobile.ControlMaster; - var isFriend = !isOwner && m_Mobile.IsPetFriend(from); - - if (!isOwner && !isFriend) - { - return; - } - - if (isFriend && order != OrderType.Follow && order != OrderType.Stay && order != OrderType.Stop) - { - return; - } - - if (order == OrderType.Attack) - { - if (target is BaseCreature creature && creature.IsScaryToPets && m_Mobile.IsScaredOfScaryThings) - { - m_Mobile.SayTo(from, "Your pet refuses to attack this creature!"); - return; - } - - if (SolenHelper.CheckRedFriendship(from) && - target is RedSolenInfiltratorQueen or RedSolenInfiltratorWarrior or RedSolenQueen or RedSolenWarrior or RedSolenWorker - || SolenHelper.CheckBlackFriendship(from) && - target is BlackSolenInfiltratorQueen or BlackSolenInfiltratorWarrior or BlackSolenQueen or BlackSolenWarrior or BlackSolenWorker) - { - from.SendAsciiMessage("You can not force your pet to attack a creature you are protected from."); - return; - } - - if (target is BaseFactionGuard) - { - m_Mobile.SayTo(from, "Your pet refuses to attack the guard."); - return; - } - } - - if (m_Mobile.CheckControlChance(from)) - { - m_Mobile.ControlTarget = target; - m_Mobile.ControlOrder = order; - } - } - - public virtual bool HandlesOnSpeech(Mobile from) - { - if (from.AccessLevel >= AccessLevel.GameMaster) - { - return true; - } - - if (from.Alive && m_Mobile.Controlled && m_Mobile.Commandable && - (from == m_Mobile.ControlMaster || m_Mobile.IsPetFriend(from))) - { - return true; - } - - return from.Alive && from.InRange(m_Mobile.Location, 3) && m_Mobile.IsHumanInTown(); - } - - public virtual void OnSpeech(SpeechEventArgs e) - { - if (e.Mobile.Alive && e.Mobile.InRange(m_Mobile.Location, 3) && m_Mobile.IsHumanInTown()) - { - if (e.HasKeyword(0x9D) && WasNamed(e.Speech)) // *move* - { - if (m_Mobile.Combatant != null) - { - // I am too busy fighting to deal with thee! - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); - } - else - { - // Excuse me? - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501516); - WalkRandomInHome(2, 2, 1); - } - } - else if (e.HasKeyword(0x9E) && WasNamed(e.Speech)) // *time* - { - if (m_Mobile.Combatant != null) - { - // I am too busy fighting to deal with thee! - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); - } - else - { - Clock.GetTime(m_Mobile, out var generalNumber, out _); - - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, generalNumber); - } - } - else if (e.HasKeyword(0x6C) && WasNamed(e.Speech)) // *train - { - if (m_Mobile.Combatant != null) - { - // I am too busy fighting to deal with thee! - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); - } - else - { - var foundSomething = false; - - var ourSkills = m_Mobile.Skills; - var theirSkills = e.Mobile.Skills; - - for (var i = 0; i < ourSkills.Length && i < theirSkills.Length; ++i) - { - var skill = ourSkills[i]; - var theirSkill = theirSkills[i]; - - if (skill?.Base >= 60.0 && m_Mobile.CheckTeach(skill.SkillName, e.Mobile)) - { - var toTeach = skill.Base / 3.0; - - if (toTeach > 42.0) - { - toTeach = 42.0; - } - - if (toTeach > theirSkill.Base) - { - var number = 1043059 + i; - - if (number > 1043107) - { - continue; - } - - if (!foundSomething) - { - m_Mobile.Say(1043058); // I can train the following: - } - - m_Mobile.Say(number); - - foundSomething = true; - } - } - } - - if (!foundSomething) - { - m_Mobile.Say(501505); // Alas, I cannot teach thee anything. - } - } - } - else - { - var toTrain = (SkillName)(-1); - - for (var i = 0; toTrain == (SkillName)(-1) && i < e.Keywords.Length; ++i) - { - var keyword = e.Keywords[i]; - - if (keyword == 0x154) - { - toTrain = SkillName.Anatomy; - } - else if (keyword >= 0x6D && keyword <= 0x9C) - { - var index = keyword - 0x6D; - - if (index >= 0 && index < m_KeywordTable.Length) - { - toTrain = m_KeywordTable[index]; - } - } - } - - if (toTrain != (SkillName)(-1) && WasNamed(e.Speech)) - { - if (m_Mobile.Combatant != null) - { - // I am too busy fighting to deal with thee! - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 501482); - } - else - { - var skills = m_Mobile.Skills; - var skill = skills[toTrain]; - - if (skill == null || skill.Base < 60.0 || !m_Mobile.CheckTeach(toTrain, e.Mobile)) - { - m_Mobile.Say(501507); // 'Tis not something I can teach thee of. - } - else - { - m_Mobile.Teach(toTrain, e.Mobile, 0, false); - } - } - } - } - } - - if (m_Mobile.Controlled && m_Mobile.Commandable) - { - m_Mobile.DebugSay("Listening..."); - - var isOwner = e.Mobile == m_Mobile.ControlMaster; - var isFriend = !isOwner && m_Mobile.IsPetFriend(e.Mobile); - - if (e.Mobile.Alive && (isOwner || isFriend)) - { - m_Mobile.DebugSay("It's from my master"); - - var keywords = e.Keywords; - var speech = e.Speech; - - // First, check the all* - for (var i = 0; i < keywords.Length; ++i) - { - var keyword = keywords[i]; - - switch (keyword) - { - case 0x164: // all come - { - if (!isOwner) - { - break; - } - - if (m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Come; - } - - return; - } - case 0x165: // all follow - { - BeginPickTarget(e.Mobile, OrderType.Follow); - return; - } - case 0x166: // all guard - case 0x16B: // all guard me - { - if (!isOwner) - { - break; - } - - if (m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Guard; - } - - return; - } - case 0x167: // all stop - { - if (m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Stop; - } - - return; - } - case 0x168: // all kill - case 0x169: // all attack - { - if (!isOwner) - { - break; - } - - BeginPickTarget(e.Mobile, OrderType.Attack); - return; - } - case 0x16C: // all follow me - { - if (m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = e.Mobile; - m_Mobile.ControlOrder = OrderType.Follow; - } - - return; - } - case 0x170: // all stay - { - if (m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Stay; - } - - return; - } - } - } - - // No all*, so check *command - for (var i = 0; i < keywords.Length; ++i) - { - var keyword = keywords[i]; - - switch (keyword) - { - case 0x155: // *come - { - if (!isOwner) - { - break; - } - - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Come; - } - - return; - } - case 0x156: // *drop - { - if (!isOwner) - { - break; - } - - if (!m_Mobile.IsDeadPet && !m_Mobile.Summoned && WasNamed(speech) && - m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Drop; - } - - return; - } - case 0x15A: // *follow - { - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - BeginPickTarget(e.Mobile, OrderType.Follow); - } - - return; - } - case 0x15B: // *friend - { - if (!isOwner) - { - break; - } - - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - if (m_Mobile.Summoned || m_Mobile is GrizzledMare) - { - e.Mobile.SendLocalizedMessage( - 1005481 - ); // Summoned creatures are loyal only to their summoners. - } - else if (e.Mobile.HasTrade) - { - e.Mobile.SendLocalizedMessage( - 1070947 - ); // You cannot friend a pet with a trade pending - } - else - { - BeginPickTarget(e.Mobile, OrderType.Friend); - } - } - - return; - } - case 0x15C: // *guard - { - if (!isOwner) - { - break; - } - - if (!m_Mobile.IsDeadPet && WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Guard; - } - - return; - } - case 0x15D: // *kill - case 0x15E: // *attack - { - if (!isOwner) - { - break; - } - - if (!m_Mobile.IsDeadPet && WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - BeginPickTarget(e.Mobile, OrderType.Attack); - } - - return; - } - case 0x15F: // *patrol - { - if (!isOwner) - { - break; - } - - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Patrol; - } - - return; - } - case 0x161: // *stop - { - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Stop; - } - - return; - } - case 0x163: // *follow me - { - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = e.Mobile; - m_Mobile.ControlOrder = OrderType.Follow; - } - - return; - } - case 0x16D: // *release - { - if (!isOwner) - { - break; - } - - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - if (!m_Mobile.Summoned) - { - e.Mobile.SendGump(new ConfirmReleaseGump(e.Mobile, m_Mobile)); - } - else - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Release; - } - } - - return; - } - case 0x16E: // *transfer - { - if (!isOwner) - { - break; - } - - if (!m_Mobile.IsDeadPet && WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - if (m_Mobile.Summoned || m_Mobile is GrizzledMare) - { - e.Mobile.SendLocalizedMessage( - 1005487 - ); // You cannot transfer ownership of a summoned creature. - } - else if (e.Mobile.HasTrade) - { - e.Mobile.SendLocalizedMessage( - 1010507 - ); // You cannot transfer a pet with a trade pending - } - else - { - BeginPickTarget(e.Mobile, OrderType.Transfer); - } - } - - return; - } - case 0x16F: // *stay - { - if (WasNamed(speech) && m_Mobile.CheckControlChance(e.Mobile)) - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Stay; - } - - return; - } - } - } - } - } - else - { - if (e.Mobile.AccessLevel >= AccessLevel.GameMaster) - { - m_Mobile.DebugSay("It's from a GM"); - - if (m_Mobile.FindMyName(e.Speech, true)) - { - var str = e.Speech.Split(' '); - int i; - - for (i = 0; i < str.Length; i++) - { - var word = str[i]; - - if (word.InsensitiveEquals("obey")) - { - m_Mobile.SetControlMaster(e.Mobile); - - if (m_Mobile.Summoned) - { - m_Mobile.SummonMaster = e.Mobile; - } - - return; - } - } - } - } - } - } - - public virtual bool Think() - { - if (m_Mobile.Deleted) - { - return false; - } - - if (CheckFlee()) - { - return true; - } - - switch (Action) - { - case ActionType.Wander: - { - m_Mobile.OnActionWander(); - return DoActionWander(); - } - - case ActionType.Combat: - { - m_Mobile.OnActionCombat(); - return DoActionCombat(); - } - - case ActionType.Guard: - { - m_Mobile.OnActionGuard(); - return DoActionGuard(); - } - - case ActionType.Flee: - { - m_Mobile.OnActionFlee(); - return DoActionFlee(); - } - - case ActionType.Interact: - { - m_Mobile.OnActionInteract(); - return DoActionInteract(); - } - - case ActionType.Backoff: - { - m_Mobile.OnActionBackoff(); - return DoActionBackoff(); - } - - default: - { - return false; - } - } - } - - public virtual void OnActionChanged() - { - switch (Action) - { - case ActionType.Wander: - { - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - m_Mobile.FocusMob = null; - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - break; - } - - case ActionType.Combat: - { - m_Mobile.Warmode = true; - m_Mobile.FocusMob = null; - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - break; - } - - case ActionType.Guard: - { - m_Mobile.Warmode = true; - m_Mobile.FocusMob = null; - m_Mobile.Combatant = null; - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - m_NextStopGuard = Core.TickCount + (int)TimeSpan.FromSeconds(10).TotalMilliseconds; - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - break; - } - - case ActionType.Flee: - { - m_Mobile.Warmode = true; - m_Mobile.FocusMob = null; - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - break; - } - - case ActionType.Interact: - { - m_Mobile.Warmode = false; - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - break; - } - - case ActionType.Backoff: - { - m_Mobile.Warmode = false; - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - break; - } - } - } - - public virtual bool OnAtWayPoint() => true; - - public virtual bool DoActionWander() - { - if (CheckHerding()) - { - m_Mobile.DebugSay("Praise the shepherd!"); - } - else if (m_Mobile.CurrentWayPoint != null) - { - var point = m_Mobile.CurrentWayPoint; - if ((point.X != m_Mobile.Location.X || point.Y != m_Mobile.Location.Y) && point.Map == m_Mobile.Map && - point.Parent == null && !point.Deleted) - { - m_Mobile.DebugSay("I will move towards my waypoint."); - DoMove(m_Mobile.GetDirectionTo(m_Mobile.CurrentWayPoint)); - } - else if (OnAtWayPoint()) - { - m_Mobile.DebugSay("I will go to the next waypoint"); - m_Mobile.CurrentWayPoint = point.NextPoint; - if (point.NextPoint?.Deleted == true) - { - m_Mobile.CurrentWayPoint = point.NextPoint = point.NextPoint.NextPoint; - } - } - } - else if (m_Mobile.IsAnimatedDead) - { - // animated dead follow their master - var master = m_Mobile.SummonMaster; - - if (master != null && master.Map == m_Mobile.Map && master.InRange(m_Mobile, m_Mobile.RangePerception)) - { - MoveTo(master, false, 1); - } - else - { - WalkRandomInHome(2, 2, 1); - } - } - else if (CheckMove()) - { - if (!m_Mobile.CheckIdle()) - { - WalkRandomInHome(2, 2, 1); - } - } - - if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && - !m_Mobile.Combatant.IsDeadBondedPet) - { - m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant); - } - - return true; - } - - public virtual bool DoActionCombat() - { - if (Core.AOS && CheckHerding()) - { - m_Mobile.DebugSay("Praise the shepherd!"); - } - else - { - var c = m_Mobile.Combatant; - - if (c?.Deleted != false || c.Map != m_Mobile.Map || !c.Alive || c.IsDeadBondedPet) - { - Action = ActionType.Wander; - } - else - { - m_Mobile.Direction = m_Mobile.GetDirectionTo(c); - } - } - - return true; - } - - public virtual bool DoActionGuard() - { - if (Core.AOS && CheckHerding()) - { - m_Mobile.DebugSay("Praise the shepherd!"); - } - else if (Core.TickCount - m_NextStopGuard < 0) - { - m_Mobile.DebugSay("I am on guard"); - // m_Mobile.Turn( Utility.Random(0, 2) - 1 ); - } - else - { - m_Mobile.DebugSay("I stopped being on guard"); - Action = ActionType.Wander; - } - - return true; - } - - public virtual bool DoActionFlee() - { - var from = m_Mobile.FocusMob; - - if (from?.Deleted != false || from.Map != m_Mobile.Map) + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have lost him"); - Action = ActionType.Guard; - return true; } - if (WalkMobileRange(from, 1, true, m_Mobile.RangePerception * 2, m_Mobile.RangePerception * 3)) + Action = ActionType.Guard; + return true; + } + + if (WalkMobileRange(from, 1, true, m_Mobile.RangePerception * 2, m_Mobile.RangePerception * 3)) + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have fled"); - Action = ActionType.Guard; - return true; } - m_Mobile.DebugSay("I am fleeing!"); - + Action = ActionType.Guard; return true; } - public virtual bool DoActionInteract() => true; - - public virtual bool DoActionBackoff() => true; - - public virtual bool Obey() + if (m_Mobile.Debug) { - return !m_Mobile.Deleted && m_Mobile.ControlOrder switch - { - OrderType.None => DoOrderNone(), - OrderType.Come => DoOrderCome(), - OrderType.Drop => DoOrderDrop(), - OrderType.Friend => DoOrderFriend(), - OrderType.Unfriend => DoOrderUnfriend(), - OrderType.Guard => DoOrderGuard(), - OrderType.Attack => DoOrderAttack(), - OrderType.Patrol => DoOrderPatrol(), - OrderType.Release => DoOrderRelease(), - OrderType.Stay => DoOrderStay(), - OrderType.Stop => DoOrderStop(), - OrderType.Follow => DoOrderFollow(), - OrderType.Transfer => DoOrderTransfer(), - _ => false - }; + m_Mobile.DebugSay("I am fleeing!"); } - public virtual void OnCurrentOrderChanged() + return true; + } + + public virtual bool DoActionInteract() => true; + + public virtual bool DoActionBackoff() => true; + + public virtual bool Obey() + { + return !m_Mobile.Deleted && m_Mobile.ControlOrder switch { - if (m_Mobile.Deleted || m_Mobile.ControlMaster?.Deleted != false) - { - return; - } + OrderType.None => DoOrderNone(), + OrderType.Come => DoOrderCome(), + OrderType.Drop => DoOrderDrop(), + OrderType.Friend => DoOrderFriend(), + OrderType.Unfriend => DoOrderUnfriend(), + OrderType.Guard => DoOrderGuard(), + OrderType.Attack => DoOrderAttack(), + OrderType.Patrol => DoOrderPatrol(), + OrderType.Release => DoOrderRelease(), + OrderType.Stay => DoOrderStay(), + OrderType.Stop => DoOrderStop(), + OrderType.Follow => DoOrderFollow(), + OrderType.Transfer => DoOrderTransfer(), + _ => false + }; + } - switch (m_Mobile.ControlOrder) - { - case OrderType.None: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.Home = m_Mobile.Location; - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Come: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Drop: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Friend: - case OrderType.Unfriend: - { - m_Mobile.ControlMaster.RevealingAction(); - break; - } - - case OrderType.Guard: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = true; - m_Mobile.Combatant = null; - m_Mobile.ControlMaster.SendLocalizedMessage(1049671, m_Mobile.Name); // ~1_PETNAME~ is now guarding you. - break; - } - - case OrderType.Attack: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - - m_Mobile.Warmode = true; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Patrol: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Release: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Stay: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Stop: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.Home = m_Mobile.Location; - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Follow: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - - case OrderType.Transfer: - { - m_Mobile.ControlMaster.RevealingAction(); - m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; - m_Mobile.PlaySound(m_Mobile.GetIdleSound()); - - m_Mobile.Warmode = false; - m_Mobile.Combatant = null; - break; - } - } + public virtual void OnCurrentOrderChanged() + { + if (m_Mobile.Deleted || m_Mobile.ControlMaster?.Deleted != false) + { + return; } - public virtual bool DoOrderNone() + switch (m_Mobile.ControlOrder) + { + case OrderType.None: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.Home = m_Mobile.Location; + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Come: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Drop: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Friend: + case OrderType.Unfriend: + { + m_Mobile.ControlMaster.RevealingAction(); + break; + } + + case OrderType.Guard: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = true; + m_Mobile.Combatant = null; + m_Mobile.ControlMaster.SendLocalizedMessage(1049671, m_Mobile.Name); // ~1_PETNAME~ is now guarding you. + break; + } + + case OrderType.Attack: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + + m_Mobile.Warmode = true; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Patrol: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Release: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Stay: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Stop: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.Home = m_Mobile.Location; + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Follow: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + + case OrderType.Transfer: + { + m_Mobile.ControlMaster.RevealingAction(); + m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed; + m_Mobile.PlaySound(m_Mobile.GetIdleSound()); + + m_Mobile.Warmode = false; + m_Mobile.Combatant = null; + break; + } + } + } + + public virtual bool DoOrderNone() + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have no order"); + } - WalkRandomInHome(3, 2, 1); + WalkRandomInHome(3, 2, 1); - if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && - !m_Mobile.Combatant.IsDeadBondedPet) - { - m_Mobile.Warmode = true; - m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant); - } - else - { - m_Mobile.Warmode = false; - } + if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && + !m_Mobile.Combatant.IsDeadBondedPet) + { + m_Mobile.Warmode = true; + m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant); + } + else + { + m_Mobile.Warmode = false; + } + return true; + } + + public virtual bool DoOrderCome() + { + if (m_Mobile.ControlMaster?.Deleted != false) + { return true; } - public virtual bool DoOrderCome() + var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m_Mobile.ControlMaster); + + if (iCurrDist > m_Mobile.RangePerception) { - if (m_Mobile.ControlMaster?.Deleted != false) + if (m_Mobile.Debug) { - return true; + m_Mobile.DebugSay("I have lost my master. I stay here"); } - var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m_Mobile.ControlMaster); + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.None; + } + else + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("My master told me come"); + } + + // Not exactly OSI style, but better than nothing. + var bRun = iCurrDist > 5; + + if (WalkMobileRange(m_Mobile.ControlMaster, 1, bRun, 0, 1)) + { + if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && + !m_Mobile.Combatant.IsDeadBondedPet) + { + m_Mobile.Warmode = true; + // m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant, bRun); + } + else + { + m_Mobile.Warmode = false; + } + } + } + + return true; + } + + public virtual bool DoOrderDrop() + { + if (m_Mobile.IsDeadPet || !m_Mobile.CanDrop) + { + return true; + } + + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I drop my stuff for my master"); + } + + var pack = m_Mobile.Backpack; + + if (pack != null) + { + var list = pack.Items; + + for (var i = list.Count - 1; i >= 0; --i) + { + if (i < list.Count) + { + list[i].MoveToWorld(m_Mobile.Location, m_Mobile.Map); + } + } + } + + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.None; + + return true; + } + + public virtual bool CheckHerding() + { + var target = m_Mobile.TargetLocation; + + if (target == null) + { + return false; // Creature is not being herded + } + + var distance = m_Mobile.GetDistanceToSqrt(target); + + if (!(distance is < 1 or > 15)) + { + DoMove(m_Mobile.GetDirectionTo(target)); + return true; + } + + if (distance < 1 && target.X == 1076 && target.Y == 450 && m_Mobile is HordeMinionFamiliar) + { + if (m_Mobile.ControlMaster is PlayerMobile pm) + { + var qs = pm.Quest; + + if (qs is DarkTidesQuest) + { + QuestObjective obj = qs.FindObjective(); + + if (obj?.Completed == false) + { + m_Mobile.AddToBackpack(new ScrollOfAbraxus()); + obj.Complete(); + } + } + } + } + + m_Mobile.TargetLocation = null; + return false; // At the target or too far away + } + + public virtual bool DoOrderFollow() + { + if (CheckHerding()) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Praise the shepherd!"); + } + } + else if (m_Mobile.ControlTarget?.Deleted == false && m_Mobile.ControlTarget != m_Mobile) + { + var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m_Mobile.ControlTarget); if (iCurrDist > m_Mobile.RangePerception) { - m_Mobile.DebugSay("I have lost my master. I stay here"); - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.None; + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I have lost the one to follow. I stay here"); + } + + if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && + !m_Mobile.Combatant.IsDeadBondedPet) + { + m_Mobile.Warmode = true; + m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant); + } + else + { + m_Mobile.Warmode = false; + } } else { - m_Mobile.DebugSay("My master told me come"); + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"My master told me to follow: {m_Mobile.ControlTarget.Name}"); + } // Not exactly OSI style, but better than nothing. var bRun = iCurrDist > 5; - if (WalkMobileRange(m_Mobile.ControlMaster, 1, bRun, 0, 1)) + if (WalkMobileRange(m_Mobile.ControlTarget, 1, bRun, 0, 1)) { if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && !m_Mobile.Combatant.IsDeadBondedPet) @@ -1243,432 +1429,342 @@ namespace Server.Mobiles else { m_Mobile.Warmode = false; + if (Core.AOS) + { + m_Mobile.CurrentSpeed = 0.1; + } } } } - - return true; } - - public virtual bool DoOrderDrop() + else { - if (m_Mobile.IsDeadPet || !m_Mobile.CanDrop) + if (m_Mobile.Debug) { - return true; - } - - m_Mobile.DebugSay("I drop my stuff for my master"); - - var pack = m_Mobile.Backpack; - - if (pack != null) - { - var list = pack.Items; - - for (var i = list.Count - 1; i >= 0; --i) - { - if (i < list.Count) - { - list[i].MoveToWorld(m_Mobile.Location, m_Mobile.Map); - } - } + m_Mobile.DebugSay("I have nobody to follow"); } m_Mobile.ControlTarget = null; m_Mobile.ControlOrder = OrderType.None; - - return true; } - public virtual bool CheckHerding() + return true; + } + + public virtual bool DoOrderFriend() + { + var from = m_Mobile.ControlMaster; + var to = m_Mobile.ControlTarget; + + if (from?.Deleted != false || to?.Deleted != false || from == to || !to.Player) { - var target = m_Mobile.TargetLocation; + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 502039); // *looks confused* + } + else + { + var youngFrom = from is PlayerMobile mobile && mobile.Young; + var youngTo = to is PlayerMobile playerMobile && playerMobile.Young; - if (target == null) + if (youngFrom && !youngTo) { - return false; // Creature is not being herded + from.SendLocalizedMessage(502040); // As a young player, you may not friend pets to older players. } - - var distance = m_Mobile.GetDistanceToSqrt(target); - - if (!(distance is < 1 or > 15)) + else if (!youngFrom && youngTo) { - DoMove(m_Mobile.GetDirectionTo(target)); - return true; + from.SendLocalizedMessage(502041); // As an older player, you may not friend pets to young players. } - - if (distance < 1 && target.X == 1076 && target.Y == 450 && m_Mobile is HordeMinionFamiliar) + else if (from.CanBeBeneficial(to, true)) { - if (m_Mobile.ControlMaster is PlayerMobile pm) + NetState fromState = from.NetState, toState = to.NetState; + + if (fromState != null && toState != null) { - var qs = pm.Quest; - - if (qs is DarkTidesQuest) + if (from.HasTrade) { - QuestObjective obj = qs.FindObjective(); - - if (obj?.Completed == false) - { - m_Mobile.AddToBackpack(new ScrollOfAbraxus()); - obj.Complete(); - } + from.SendLocalizedMessage(1070947); // You cannot friend a pet with a trade pending } - } - } - - m_Mobile.TargetLocation = null; - return false; // At the target or too far away - } - - public virtual bool DoOrderFollow() - { - if (CheckHerding()) - { - m_Mobile.DebugSay("Praise the shepherd!"); - } - else if (m_Mobile.ControlTarget?.Deleted == false && m_Mobile.ControlTarget != m_Mobile) - { - var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m_Mobile.ControlTarget); - - if (iCurrDist > m_Mobile.RangePerception) - { - m_Mobile.DebugSay("I have lost the one to follow. I stay here"); - - if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && - !m_Mobile.Combatant.IsDeadBondedPet) + else if (to.HasTrade) { - m_Mobile.Warmode = true; - m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant); + to.SendLocalizedMessage(1070947); // You cannot friend a pet with a trade pending + } + else if (m_Mobile.IsPetFriend(to)) + { + from.SendLocalizedMessage(1049691); // That person is already a friend. + } + else if (!m_Mobile.AllowNewPetFriend) + { + from.SendLocalizedMessage( + 1005482 + ); // Your pet does not seem to be interested in making new friends right now. } else { - m_Mobile.Warmode = false; - } - } - else - { - m_Mobile.DebugSay("My master told me to follow: {0}", m_Mobile.ControlTarget.Name); + // ~1_NAME~ will now accept movement commands from ~2_NAME~. + from.SendLocalizedMessage(1049676, $"{m_Mobile.Name}\t{to.Name}"); - // Not exactly OSI style, but better than nothing. - var bRun = iCurrDist > 5; + /* ~1_NAME~ has granted you the ability to give orders to their pet ~2_PET_NAME~. + * This creature will now consider you as a friend. + */ + to.SendLocalizedMessage(1043246, $"{from.Name}\t{m_Mobile.Name}"); - if (WalkMobileRange(m_Mobile.ControlTarget, 1, bRun, 0, 1)) - { - if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive && - !m_Mobile.Combatant.IsDeadBondedPet) - { - m_Mobile.Warmode = true; - // m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.Combatant, bRun); - } - else - { - m_Mobile.Warmode = false; - if (Core.AOS) - { - m_Mobile.CurrentSpeed = 0.1; - } - } + m_Mobile.AddPetFriend(to); + + m_Mobile.ControlTarget = to; + m_Mobile.ControlOrder = OrderType.Follow; + + return true; } } } - else - { - m_Mobile.DebugSay("I have nobody to follow"); - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.None; - } + } + m_Mobile.ControlTarget = from; + m_Mobile.ControlOrder = OrderType.Follow; + + return true; + } + + public virtual bool DoOrderUnfriend() + { + var from = m_Mobile.ControlMaster; + var to = m_Mobile.ControlTarget; + + if (from?.Deleted != false || to?.Deleted != false || from == to || !to.Player) + { + m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 502039); // *looks confused* + } + else if (!m_Mobile.IsPetFriend(to)) + { + from.SendLocalizedMessage(1070953); // That person is not a friend. + } + else + { + // ~1_NAME~ will no longer accept movement commands from ~2_NAME~. + from.SendLocalizedMessage(1070951, $"{m_Mobile.Name}\t{to.Name}"); + + /* ~1_NAME~ has no longer granted you the ability to give orders to their pet ~2_PET_NAME~. + * This creature will no longer consider you as a friend. + */ + to.SendLocalizedMessage(1070952, $"{from.Name}\t{m_Mobile.Name}"); + + m_Mobile.RemovePetFriend(to); + } + + m_Mobile.ControlTarget = from; + m_Mobile.ControlOrder = OrderType.Follow; + + return true; + } + + public virtual bool DoOrderGuard() + { + if (m_Mobile.IsDeadPet) + { return true; } - public virtual bool DoOrderFriend() + var controlMaster = m_Mobile.ControlMaster; + + if (controlMaster?.Deleted != false) { - var from = m_Mobile.ControlMaster; - var to = m_Mobile.ControlTarget; + return true; + } - if (from?.Deleted != false || to?.Deleted != false || from == to || !to.Player) + var combatant = m_Mobile.Combatant; + + var aggressors = controlMaster.Aggressors; + + if (aggressors.Count > 0) + { + for (var i = 0; i < aggressors.Count; ++i) { - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 502039); // *looks confused* - } - else - { - var youngFrom = from is PlayerMobile mobile && mobile.Young; - var youngTo = to is PlayerMobile playerMobile && playerMobile.Young; + var info = aggressors[i]; + var attacker = info.Attacker; - if (youngFrom && !youngTo) + if (attacker?.Deleted == false && + attacker.GetDistanceToSqrt(m_Mobile) <= m_Mobile.RangePerception) { - from.SendLocalizedMessage(502040); // As a young player, you may not friend pets to older players. - } - else if (!youngFrom && youngTo) - { - from.SendLocalizedMessage(502041); // As an older player, you may not friend pets to young players. - } - else if (from.CanBeBeneficial(to, true)) - { - NetState fromState = from.NetState, toState = to.NetState; - - if (fromState != null && toState != null) + if (combatant == null || attacker.GetDistanceToSqrt(controlMaster) < + combatant.GetDistanceToSqrt(controlMaster)) { - if (from.HasTrade) - { - from.SendLocalizedMessage(1070947); // You cannot friend a pet with a trade pending - } - else if (to.HasTrade) - { - to.SendLocalizedMessage(1070947); // You cannot friend a pet with a trade pending - } - else if (m_Mobile.IsPetFriend(to)) - { - from.SendLocalizedMessage(1049691); // That person is already a friend. - } - else if (!m_Mobile.AllowNewPetFriend) - { - from.SendLocalizedMessage( - 1005482 - ); // Your pet does not seem to be interested in making new friends right now. - } - else - { - // ~1_NAME~ will now accept movement commands from ~2_NAME~. - from.SendLocalizedMessage(1049676, $"{m_Mobile.Name}\t{to.Name}"); - - /* ~1_NAME~ has granted you the ability to give orders to their pet ~2_PET_NAME~. - * This creature will now consider you as a friend. - */ - to.SendLocalizedMessage(1043246, $"{from.Name}\t{m_Mobile.Name}"); - - m_Mobile.AddPetFriend(to); - - m_Mobile.ControlTarget = to; - m_Mobile.ControlOrder = OrderType.Follow; - - return true; - } + combatant = attacker; } } } - m_Mobile.ControlTarget = from; - m_Mobile.ControlOrder = OrderType.Follow; - - return true; + if (combatant != null && m_Mobile.Debug) + { + m_Mobile.DebugSay("Crap, my master has been attacked! I will attack one of those bastards!"); + } } - public virtual bool DoOrderUnfriend() + if (combatant?.Deleted == false && combatant != m_Mobile && combatant != m_Mobile.ControlMaster && + combatant.Alive && !combatant.IsDeadBondedPet && m_Mobile.CanSee(combatant) && + m_Mobile.CanBeHarmful(combatant, false) && combatant.Map == m_Mobile.Map) { - var from = m_Mobile.ControlMaster; - var to = m_Mobile.ControlTarget; - - if (from?.Deleted != false || to?.Deleted != false || from == to || !to.Player) - { - m_Mobile.PublicOverheadMessage(MessageType.Regular, 0x3B2, 502039); // *looks confused* - } - else if (!m_Mobile.IsPetFriend(to)) - { - from.SendLocalizedMessage(1070953); // That person is not a friend. - } - else - { - // ~1_NAME~ will no longer accept movement commands from ~2_NAME~. - from.SendLocalizedMessage(1070951, $"{m_Mobile.Name}\t{to.Name}"); - - /* ~1_NAME~ has no longer granted you the ability to give orders to their pet ~2_PET_NAME~. - * This creature will no longer consider you as a friend. - */ - to.SendLocalizedMessage(1070952, $"{from.Name}\t{m_Mobile.Name}"); - - m_Mobile.RemovePetFriend(to); - } - - m_Mobile.ControlTarget = from; - m_Mobile.ControlOrder = OrderType.Follow; - - return true; - } - - public virtual bool DoOrderGuard() - { - if (m_Mobile.IsDeadPet) - { - return true; - } - - var controlMaster = m_Mobile.ControlMaster; - - if (controlMaster?.Deleted != false) - { - return true; - } - - var combatant = m_Mobile.Combatant; - - var aggressors = controlMaster.Aggressors; - - if (aggressors.Count > 0) - { - for (var i = 0; i < aggressors.Count; ++i) - { - var info = aggressors[i]; - var attacker = info.Attacker; - - if (attacker?.Deleted == false && - attacker.GetDistanceToSqrt(m_Mobile) <= m_Mobile.RangePerception) - { - if (combatant == null || attacker.GetDistanceToSqrt(controlMaster) < - combatant.GetDistanceToSqrt(controlMaster)) - { - combatant = attacker; - } - } - } - - if (combatant != null) - { - m_Mobile.DebugSay("Crap, my master has been attacked! I will attack one of those bastards!"); - } - } - - if (combatant?.Deleted == false && combatant != m_Mobile && combatant != m_Mobile.ControlMaster && - combatant.Alive && !combatant.IsDeadBondedPet && m_Mobile.CanSee(combatant) && - m_Mobile.CanBeHarmful(combatant, false) && combatant.Map == m_Mobile.Map) + if (m_Mobile.Debug) { m_Mobile.DebugSay("Guarding from target..."); - - m_Mobile.Combatant = combatant; - m_Mobile.FocusMob = combatant; - Action = ActionType.Combat; - - /* - * We need to call Think() here or spell casting monsters will not use - * spells when guarding because their target is never processed. - */ - Think(); } - else + + m_Mobile.Combatant = combatant; + m_Mobile.FocusMob = combatant; + Action = ActionType.Combat; + + /* + * We need to call Think() here or spell casting monsters will not use + * spells when guarding because their target is never processed. + */ + Think(); + } + else + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("Nothing to guard from"); - - m_Mobile.Warmode = false; - if (Core.AOS) - { - m_Mobile.CurrentSpeed = 0.1; - } - - WalkMobileRange(controlMaster, 1, false, 0, 1); } + m_Mobile.Warmode = false; + if (Core.AOS) + { + m_Mobile.CurrentSpeed = 0.1; + } + + WalkMobileRange(controlMaster, 1, false, 0, 1); + } + + return true; + } + + public virtual bool DoOrderAttack() + { + if (m_Mobile.IsDeadPet) + { return true; } - public virtual bool DoOrderAttack() + if (m_Mobile.ControlTarget?.Deleted != false || m_Mobile.ControlTarget.Map != m_Mobile.Map || + !m_Mobile.ControlTarget.Alive || m_Mobile.ControlTarget.IsDeadBondedPet) { - if (m_Mobile.IsDeadPet) - { - return true; - } - - if (m_Mobile.ControlTarget?.Deleted != false || m_Mobile.ControlTarget.Map != m_Mobile.Map || - !m_Mobile.ControlTarget.Alive || m_Mobile.ControlTarget.IsDeadBondedPet) + if (m_Mobile.Debug) { m_Mobile.DebugSay( "I think he might be dead. He's not anywhere around here at least. That's cool. I'm glad he's dead." ); + } - if (Core.AOS) - { - m_Mobile.ControlTarget = m_Mobile.ControlMaster; - m_Mobile.ControlOrder = OrderType.Follow; - } - else - { - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.None; - } - - if (m_Mobile.FightMode is FightMode.Closest or FightMode.Aggressor) - { - Mobile newCombatant = null; - var newScore = 0.0; - - foreach (var aggr in m_Mobile.GetMobilesInRange(m_Mobile.RangePerception)) - { - if (!m_Mobile.CanSee(aggr) || aggr.Combatant != m_Mobile) - { - continue; - } - - if (aggr.IsDeadBondedPet || !aggr.Alive) - { - continue; - } - - var aggrScore = m_Mobile.GetFightModeRanking(aggr, FightMode.Closest, false); - - if ((newCombatant == null || aggrScore > newScore) && m_Mobile.InLOS(aggr)) - { - newCombatant = aggr; - newScore = aggrScore; - } - } - - if (newCombatant != null) - { - m_Mobile.ControlTarget = newCombatant; - m_Mobile.ControlOrder = OrderType.Attack; - m_Mobile.Combatant = newCombatant; - m_Mobile.DebugSay("But -that- is not dead. Here we go again..."); - Think(); - } - } + if (Core.AOS) + { + m_Mobile.ControlTarget = m_Mobile.ControlMaster; + m_Mobile.ControlOrder = OrderType.Follow; } else { - m_Mobile.DebugSay("Attacking target..."); - Think(); + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.None; } - return true; + if (m_Mobile.FightMode is FightMode.Closest or FightMode.Aggressor) + { + Mobile newCombatant = null; + var newScore = 0.0; + + foreach (var aggr in m_Mobile.GetMobilesInRange(m_Mobile.RangePerception)) + { + if (!m_Mobile.CanSee(aggr) || aggr.Combatant != m_Mobile) + { + continue; + } + + if (aggr.IsDeadBondedPet || !aggr.Alive) + { + continue; + } + + var aggrScore = m_Mobile.GetFightModeRanking(aggr, FightMode.Closest, false); + + if ((newCombatant == null || aggrScore > newScore) && m_Mobile.InLOS(aggr)) + { + newCombatant = aggr; + newScore = aggrScore; + } + } + + if (newCombatant != null) + { + m_Mobile.ControlTarget = newCombatant; + m_Mobile.ControlOrder = OrderType.Attack; + m_Mobile.Combatant = newCombatant; + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("But -that- is not dead. Here we go again..."); + } + + Think(); + } + } + } + else + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Attacking target..."); + } + + Think(); } - public virtual bool DoOrderPatrol() + return true; + } + + public virtual bool DoOrderPatrol() + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("This order is not yet coded"); - return true; } - public virtual bool DoOrderRelease() + return true; + } + + public virtual bool DoOrderRelease() + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have been released"); - - m_Mobile.PlaySound(m_Mobile.GetAngerSound()); - - m_Mobile.SetControlMaster(null); - m_Mobile.SummonMaster = null; - - m_Mobile.BondingBegin = DateTime.MinValue; - m_Mobile.OwnerAbandonTime = DateTime.MinValue; - m_Mobile.IsBonded = false; - - var spawner = m_Mobile.Spawner; - - if (spawner != null && spawner.HomeLocation != Point3D.Zero) - { - m_Mobile.Home = spawner.HomeLocation; - m_Mobile.RangeHome = spawner.HomeRange; - } - - if (m_Mobile.DeleteOnRelease || m_Mobile.IsDeadPet) - { - m_Mobile.Delete(); - } - - m_Mobile.BeginDeleteTimer(); - m_Mobile.DropBackpack(); - - return true; } - public virtual bool DoOrderStay() + m_Mobile.PlaySound(m_Mobile.GetAngerSound()); + + m_Mobile.SetControlMaster(null); + m_Mobile.SummonMaster = null; + + m_Mobile.BondingBegin = DateTime.MinValue; + m_Mobile.OwnerAbandonTime = DateTime.MinValue; + m_Mobile.IsBonded = false; + + var spawner = m_Mobile.Spawner; + + if (spawner != null && spawner.HomeLocation != Point3D.Zero) + { + m_Mobile.Home = spawner.HomeLocation; + m_Mobile.RangeHome = spawner.HomeRange; + } + + if (m_Mobile.DeleteOnRelease || m_Mobile.IsDeadPet) + { + m_Mobile.Delete(); + } + + m_Mobile.BeginDeleteTimer(); + m_Mobile.DropBackpack(); + + return true; + } + + public virtual bool DoOrderStay() + { + if (m_Mobile.Debug) { if (CheckHerding()) { @@ -1678,146 +1774,182 @@ namespace Server.Mobiles { m_Mobile.DebugSay("My master told me to stay"); } + } + return true; + } + + public virtual bool DoOrderStop() + { + if (m_Mobile.ControlMaster?.Deleted != false) + { return true; } - public virtual bool DoOrderStop() + if (m_Mobile.Debug) { - if (m_Mobile.ControlMaster?.Deleted != false) + m_Mobile.DebugSay("My master told me to stop."); + } + + m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.ControlMaster); + m_Mobile.Home = m_Mobile.Location; + + m_Mobile.ControlTarget = null; + + if (Core.ML) + { + WalkRandomInHome(3, 2, 1); + } + else + { + m_Mobile.ControlOrder = OrderType.None; + } + + return true; + } + + public virtual bool DoOrderTransfer() + { + if (m_Mobile.IsDeadPet) + { + return true; + } + + var from = m_Mobile.ControlMaster; + var to = m_Mobile.ControlTarget; + + if (from?.Deleted == false && to?.Deleted == false && from != to && to.Player) + { + if (m_Mobile.Debug) { - return true; + m_Mobile.DebugSay($"Begin transfer with {to.Name}"); } - m_Mobile.DebugSay("My master told me to stop."); + var youngFrom = from is PlayerMobile mobile && mobile.Young; + var youngTo = to is PlayerMobile playerMobile && playerMobile.Young; - m_Mobile.Direction = m_Mobile.GetDirectionTo(m_Mobile.ControlMaster); - m_Mobile.Home = m_Mobile.Location; - - m_Mobile.ControlTarget = null; - - if (Core.ML) + if (youngFrom && !youngTo) { - WalkRandomInHome(3, 2, 1); + from.SendLocalizedMessage(502051); // As a young player, you may not transfer pets to older players. + } + else if (!youngFrom && youngTo) + { + from.SendLocalizedMessage(502052); // As an older player, you may not transfer pets to young players. + } + else if (!m_Mobile.CanBeControlledBy(to)) + { + var args = $"{to.Name}\t{from.Name}\t "; + + from.SendLocalizedMessage( + 1043248, + args + ); // The pet refuses to be transferred because it will not obey ~1_NAME~.~3_BLANK~ + to.SendLocalizedMessage( + 1043249, + args + ); // The pet will not accept you as a master because it does not trust you.~3_BLANK~ + } + else if (!m_Mobile.CanBeControlledBy(from)) + { + var args = $"{to.Name}\t{from.Name}\t "; + + from.SendLocalizedMessage( + 1043250, + args + ); // The pet refuses to be transferred because it will not obey you sufficiently.~3_BLANK~ + to.SendLocalizedMessage( + 1043251, + args + ); // The pet will not accept you as a master because it does not trust ~2_NAME~.~3_BLANK~ + } + else if (TransferItem.IsInCombat(m_Mobile)) + { + from.SendMessage("You may not transfer a pet that has recently been in combat."); + to.SendMessage("The pet may not be transferred to you because it has recently been in combat."); } else { - m_Mobile.ControlOrder = OrderType.None; - } + NetState fromState = from.NetState, toState = to.NetState; - return true; - } - - public virtual bool DoOrderTransfer() - { - if (m_Mobile.IsDeadPet) - { - return true; - } - - var from = m_Mobile.ControlMaster; - var to = m_Mobile.ControlTarget; - - if (from?.Deleted == false && to?.Deleted == false && from != to && to.Player) - { - m_Mobile.DebugSay("Begin transfer with {0}", to.Name); - - var youngFrom = from is PlayerMobile mobile && mobile.Young; - var youngTo = to is PlayerMobile playerMobile && playerMobile.Young; - - if (youngFrom && !youngTo) + if (fromState != null && toState != null) { - from.SendLocalizedMessage(502051); // As a young player, you may not transfer pets to older players. - } - else if (!youngFrom && youngTo) - { - from.SendLocalizedMessage(502052); // As an older player, you may not transfer pets to young players. - } - else if (!m_Mobile.CanBeControlledBy(to)) - { - var args = $"{to.Name}\t{from.Name}\t "; - - from.SendLocalizedMessage( - 1043248, - args - ); // The pet refuses to be transferred because it will not obey ~1_NAME~.~3_BLANK~ - to.SendLocalizedMessage( - 1043249, - args - ); // The pet will not accept you as a master because it does not trust you.~3_BLANK~ - } - else if (!m_Mobile.CanBeControlledBy(from)) - { - var args = $"{to.Name}\t{from.Name}\t "; - - from.SendLocalizedMessage( - 1043250, - args - ); // The pet refuses to be transferred because it will not obey you sufficiently.~3_BLANK~ - to.SendLocalizedMessage( - 1043251, - args - ); // The pet will not accept you as a master because it does not trust ~2_NAME~.~3_BLANK~ - } - else if (TransferItem.IsInCombat(m_Mobile)) - { - from.SendMessage("You may not transfer a pet that has recently been in combat."); - to.SendMessage("The pet may not be transferred to you because it has recently been in combat."); - } - else - { - NetState fromState = from.NetState, toState = to.NetState; - - if (fromState != null && toState != null) + if (from.HasTrade) { - if (from.HasTrade) - { - from.SendLocalizedMessage(1010507); // You cannot transfer a pet with a trade pending - } - else if (to.HasTrade) - { - to.SendLocalizedMessage(1010507); // You cannot transfer a pet with a trade pending - } - else - { - Container c = fromState.AddTrade(toState); - c.DropItem(new TransferItem(m_Mobile)); - } + from.SendLocalizedMessage(1010507); // You cannot transfer a pet with a trade pending + } + else if (to.HasTrade) + { + to.SendLocalizedMessage(1010507); // You cannot transfer a pet with a trade pending + } + else + { + Container c = fromState.AddTrade(toState); + c.DropItem(new TransferItem(m_Mobile)); } } } - - m_Mobile.ControlTarget = null; - m_Mobile.ControlOrder = OrderType.Stay; - - return true; } - public virtual bool DoBardPacified() + m_Mobile.ControlTarget = null; + m_Mobile.ControlOrder = OrderType.Stay; + + return true; + } + + public virtual bool DoBardPacified() + { + if (Core.Now < m_Mobile.BardEndTime) { - if (Core.Now < m_Mobile.BardEndTime) + if (m_Mobile.Debug) { m_Mobile.DebugSay("I am pacified, I wait"); - m_Mobile.Combatant = null; - m_Mobile.Warmode = false; } - else + + m_Mobile.Combatant = null; + m_Mobile.Warmode = false; + } + else + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I'm not pacified any longer"); - m_Mobile.BardPacified = false; } - return true; + m_Mobile.BardPacified = false; } - public virtual bool DoBardProvoked() + return true; + } + + public virtual bool DoBardProvoked() + { + if (Core.Now >= m_Mobile.BardEndTime && + (m_Mobile.BardMaster?.Deleted != false || + m_Mobile.BardMaster.Map != m_Mobile.Map || m_Mobile.GetDistanceToSqrt(m_Mobile.BardMaster) > + m_Mobile.RangePerception)) { - if (Core.Now >= m_Mobile.BardEndTime && - (m_Mobile.BardMaster?.Deleted != false || - m_Mobile.BardMaster.Map != m_Mobile.Map || m_Mobile.GetDistanceToSqrt(m_Mobile.BardMaster) > - m_Mobile.RangePerception)) + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have lost my provoker"); + } + + m_Mobile.BardProvoked = false; + m_Mobile.BardMaster = null; + m_Mobile.BardTarget = null; + + m_Mobile.Combatant = null; + m_Mobile.Warmode = false; + } + else + { + if (m_Mobile.BardTarget?.Deleted != false || m_Mobile.BardTarget.Map != m_Mobile.Map || + m_Mobile.GetDistanceToSqrt(m_Mobile.BardTarget) > m_Mobile.RangePerception) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I have lost my provoke target"); + } + m_Mobile.BardProvoked = false; m_Mobile.BardMaster = null; m_Mobile.BardTarget = null; @@ -1827,502 +1959,519 @@ namespace Server.Mobiles } else { - if (m_Mobile.BardTarget?.Deleted != false || m_Mobile.BardTarget.Map != m_Mobile.Map || - m_Mobile.GetDistanceToSqrt(m_Mobile.BardTarget) > m_Mobile.RangePerception) - { - m_Mobile.DebugSay("I have lost my provoke target"); - m_Mobile.BardProvoked = false; - m_Mobile.BardMaster = null; - m_Mobile.BardTarget = null; + m_Mobile.Combatant = m_Mobile.BardTarget; + m_Action = ActionType.Combat; - m_Mobile.Combatant = null; - m_Mobile.Warmode = false; + m_Mobile.OnThink(); + Think(); + } + } + + return true; + } + + public virtual void WalkRandom(int iChanceToNotMove, int iChanceToDir, int iSteps) + { + if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves) + { + return; + } + + for (var i = 0; i < iSteps; i++) + { + if (Utility.Random(8 * iChanceToNotMove) <= 8) + { + var iRndMove = Utility.Random(0, 8 + 9 * iChanceToDir); + + switch (iRndMove) + { + case 0: + { + DoMove(Direction.Up); + break; + } + case 1: + { + DoMove(Direction.North); + break; + } + case 2: + { + DoMove(Direction.Left); + break; + } + case 3: + { + DoMove(Direction.West); + break; + } + case 5: + { + DoMove(Direction.Down); + break; + } + case 6: + { + DoMove(Direction.South); + break; + } + case 7: + { + DoMove(Direction.Right); + break; + } + case 8: + { + DoMove(Direction.East); + break; + } + default: + { + DoMove(m_Mobile.Direction); + break; + } + } + } + } + } + + public double TransformMoveDelay(double delay) + { + // Monster is passive + if (m_Mobile is { Controlled: false, Summoned: false } && Math.Abs(delay - m_Mobile.PassiveSpeed) < 0.0001) + { + delay *= 3; + } + + if (!m_Mobile.IsDeadPet && (m_Mobile.ReduceSpeedWithDamage || m_Mobile.IsSubdued)) + { + int stats, statsMax; + if (Core.HS) + { + stats = m_Mobile.Stam; + statsMax = m_Mobile.StamMax; + } + else + { + stats = m_Mobile.Hits; + statsMax = m_Mobile.HitsMax; + } + + var offset = statsMax <= 0 ? 1.0 : Math.Max(0, stats) / (double)statsMax; + + if (offset < 1.0) + { + delay += m_Mobile.PassiveSpeed * (1.0 - offset); + } + } + + return delay; + } + + public virtual bool CheckMove() => Core.TickCount - NextMove >= 0; + + public virtual bool DoMove(Direction d, bool badStateOk = false) + { + var res = DoMoveImpl(d); + + return res is MoveResult.Success or MoveResult.SuccessAutoTurn || badStateOk && res == MoveResult.BadState; + } + + public virtual MoveResult DoMoveImpl(Direction d) + { + if (m_Mobile.Deleted || m_Mobile.Frozen || m_Mobile.Paralyzed || + m_Mobile.Spell?.IsCasting == true || m_Mobile.DisallowAllMoves) + { + return MoveResult.BadState; + } + + if (!CheckMove()) + { + return MoveResult.BadState; + } + + // This makes them always move one step, never any direction changes + // TODO: This is firing off deltas which aren't needed. Look into replacing/removing this + m_Mobile.Direction = d; + + var delay = (int)(TransformMoveDelay(m_Mobile.CurrentSpeed) * 1000); + NextMove += delay; + + if (Core.TickCount - NextMove > 0) + { + NextMove = Core.TickCount; + } + + m_Mobile.Pushing = false; + + MoveImpl.IgnoreMovableImpassables = m_Mobile.CanMoveOverObstacles && !m_Mobile.CanDestroyObstacles; + + if ((m_Mobile.Direction & Direction.Mask) != (d & Direction.Mask)) + { + var v = m_Mobile.Move(d); + + MoveImpl.IgnoreMovableImpassables = false; + return v ? MoveResult.Success : MoveResult.Blocked; + } + + if (m_Mobile.Move(d)) + { + MoveImpl.IgnoreMovableImpassables = false; + return MoveResult.Success; + } + + var wasPushing = m_Mobile.Pushing; + + var blocked = true; + + var canOpenDoors = m_Mobile.CanOpenDoors; + var canDestroyObstacles = m_Mobile.CanDestroyObstacles; + + if (canOpenDoors || canDestroyObstacles) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("My movement was blocked, I will try to clear some obstacles."); + } + + var map = m_Mobile.Map; + + if (map != null) + { + int x = m_Mobile.X, y = m_Mobile.Y; + Movement.Movement.Offset(d, ref x, ref y); + + var destroyables = 0; + + var eable = map.GetItemsInRange(new Point3D(x, y, m_Mobile.Location.Z), 1); + + foreach (var item in eable) + { + if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > m_Mobile.Z && + m_Mobile.Z + 16 > door.Z) + { + if (door.X != x || door.Y != y) + { + continue; + } + + if (!door.Locked || !door.UseLocks()) + { + m_Obstacles.Enqueue(door); + } + + if (!canDestroyObstacles) + { + break; + } + } + else if (canDestroyObstacles && item.Movable && item.ItemData.Impassable && + item.Z + item.ItemData.Height > m_Mobile.Z && m_Mobile.Z + 16 > item.Z) + { + if (!m_Mobile.InRange(item.GetWorldLocation(), 1)) + { + continue; + } + + m_Obstacles.Enqueue(item); + ++destroyables; + } + } + + eable.Free(); + + if (destroyables > 0) + { + Effects.PlaySound(new Point3D(x, y, m_Mobile.Z), m_Mobile.Map, 0x3B3); + } + + if (m_Obstacles.Count > 0) + { + blocked = false; // retry movement + } + + while (m_Obstacles.Count > 0) + { + var item = m_Obstacles.Dequeue(); + + if (item is BaseDoor door) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay( + "Little do they expect, I've learned how to open doors. Didn't they read the script??" + ); + } + + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("*twist*"); + } + + door.Use(m_Mobile); + } + else + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay( + $"Ugabooga. I'm so big and tough I can destroy it: {item.GetType().Name}" + ); + } + + if (item is Container cont) + { + for (var i = 0; i < cont.Items.Count; ++i) + { + var check = cont.Items[i]; + + if (check.Movable && check.ItemData.Impassable && + cont.Z + check.ItemData.Height > m_Mobile.Z) + { + m_Obstacles.Enqueue(check); + } + } + + cont.Destroy(); + } + else + { + item.Delete(); + } + } + } + + if (!blocked) + { + blocked = !m_Mobile.Move(d); + } + } + } + + if (blocked) + { + var offset = Utility.RandomDouble() >= 0.6 ? 1 : -1; + + for (var i = 0; i < 2; ++i) + { + m_Mobile.TurnInternal(offset); + + if (m_Mobile.Move(m_Mobile.Direction)) + { + MoveImpl.IgnoreMovableImpassables = false; + return MoveResult.SuccessAutoTurn; + } + } + + MoveImpl.IgnoreMovableImpassables = false; + return wasPushing ? MoveResult.BadState : MoveResult.Blocked; + } + + MoveImpl.IgnoreMovableImpassables = false; + return MoveResult.Success; + } + + public virtual void WalkRandomInHome(int iChanceToNotMove, int iChanceToDir, int iSteps) + { + if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves) + { + return; + } + + if (m_Mobile.Home == Point3D.Zero) + { + if (m_Mobile.Spawner is RegionSpawner rs) + { + Region region = rs.SpawnRegion; + + if (m_Mobile.Region.AcceptsSpawnsFrom(region)) + { + m_Mobile.WalkRegion = region; + WalkRandom(iChanceToNotMove, iChanceToDir, iSteps); + m_Mobile.WalkRegion = null; } else { - m_Mobile.Combatant = m_Mobile.BardTarget; - m_Action = ActionType.Combat; - - m_Mobile.OnThink(); - Think(); + if (region.GoLocation != Point3D.Zero && Utility.Random(10) > 5) + { + DoMove(m_Mobile.GetDirectionTo(region.GoLocation)); + } + else + { + WalkRandom(iChanceToNotMove, iChanceToDir, 1); + } } } + else + { + WalkRandom(iChanceToNotMove, iChanceToDir, iSteps); + } + } + else + { + for (var i = 0; i < iSteps; i++) + { + if (m_Mobile.RangeHome != 0) + { + var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m_Mobile.Home); + + if (iCurrDist < m_Mobile.RangeHome * 2 / 3) + { + WalkRandom(iChanceToNotMove, iChanceToDir, 1); + } + else if (iCurrDist > m_Mobile.RangeHome) + { + DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home)); + } + else + { + if (Utility.Random(10) > 5) + { + DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home)); + } + else + { + WalkRandom(iChanceToNotMove, iChanceToDir, 1); + } + } + } + else + { + if (m_Mobile.Location != m_Mobile.Home) + { + DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home)); + } + } + } + } + } + + public virtual bool CheckFlee() + { + if (m_Mobile.CheckFlee()) + { + var combatant = m_Mobile.Combatant; + + if (combatant == null) + { + WalkRandom(1, 2, 1); + } + else + { + var d = combatant.GetDirectionTo(m_Mobile); + + d = (Direction)((int)d + Utility.RandomMinMax(-1, +1)); + + m_Mobile.Direction = d; + m_Mobile.Move(d); + } return true; } - public virtual void WalkRandom(int iChanceToNotMove, int iChanceToDir, int iSteps) + return false; + } + + public virtual void OnTeleported() + { + if (m_Path != null) { - if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves) - { - return; - } - - for (var i = 0; i < iSteps; i++) - { - if (Utility.Random(8 * iChanceToNotMove) <= 8) - { - var iRndMove = Utility.Random(0, 8 + 9 * iChanceToDir); - - switch (iRndMove) - { - case 0: - { - DoMove(Direction.Up); - break; - } - case 1: - { - DoMove(Direction.North); - break; - } - case 2: - { - DoMove(Direction.Left); - break; - } - case 3: - { - DoMove(Direction.West); - break; - } - case 5: - { - DoMove(Direction.Down); - break; - } - case 6: - { - DoMove(Direction.South); - break; - } - case 7: - { - DoMove(Direction.Right); - break; - } - case 8: - { - DoMove(Direction.East); - break; - } - default: - { - DoMove(m_Mobile.Direction); - break; - } - } - } - } - } - - public double TransformMoveDelay(double delay) - { - // Monster is passive - if (m_Mobile is { Controlled: false, Summoned: false } && Math.Abs(delay - m_Mobile.PassiveSpeed) < 0.0001) - { - delay *= 3; - } - - if (!m_Mobile.IsDeadPet && (m_Mobile.ReduceSpeedWithDamage || m_Mobile.IsSubdued)) - { - int stats, statsMax; - if (Core.HS) - { - stats = m_Mobile.Stam; - statsMax = m_Mobile.StamMax; - } - else - { - stats = m_Mobile.Hits; - statsMax = m_Mobile.HitsMax; - } - - var offset = statsMax <= 0 ? 1.0 : Math.Max(0, stats) / (double)statsMax; - - if (offset < 1.0) - { - delay += m_Mobile.PassiveSpeed * (1.0 - offset); - } - } - - return delay; - } - - public virtual bool CheckMove() => Core.TickCount - NextMove >= 0; - - public virtual bool DoMove(Direction d, bool badStateOk = false) - { - var res = DoMoveImpl(d); - - return res is MoveResult.Success or MoveResult.SuccessAutoTurn || badStateOk && res == MoveResult.BadState; - } - - public virtual MoveResult DoMoveImpl(Direction d) - { - if (m_Mobile.Deleted || m_Mobile.Frozen || m_Mobile.Paralyzed || - m_Mobile.Spell?.IsCasting == true || m_Mobile.DisallowAllMoves) - { - return MoveResult.BadState; - } - - if (!CheckMove()) - { - return MoveResult.BadState; - } - - // This makes them always move one step, never any direction changes - // TODO: This is firing off deltas which aren't needed. Look into replacing/removing this - m_Mobile.Direction = d; - - var delay = (int)(TransformMoveDelay(m_Mobile.CurrentSpeed) * 1000); - NextMove += delay; - - if (Core.TickCount - NextMove > 0) - { - NextMove = Core.TickCount; - } - - m_Mobile.Pushing = false; - - MoveImpl.IgnoreMovableImpassables = m_Mobile.CanMoveOverObstacles && !m_Mobile.CanDestroyObstacles; - - if ((m_Mobile.Direction & Direction.Mask) != (d & Direction.Mask)) - { - var v = m_Mobile.Move(d); - - MoveImpl.IgnoreMovableImpassables = false; - return v ? MoveResult.Success : MoveResult.Blocked; - } - - if (m_Mobile.Move(d)) - { - MoveImpl.IgnoreMovableImpassables = false; - return MoveResult.Success; - } - - var wasPushing = m_Mobile.Pushing; - - var blocked = true; - - var canOpenDoors = m_Mobile.CanOpenDoors; - var canDestroyObstacles = m_Mobile.CanDestroyObstacles; - - if (canOpenDoors || canDestroyObstacles) - { - m_Mobile.DebugSay("My movement was blocked, I will try to clear some obstacles."); - - var map = m_Mobile.Map; - - if (map != null) - { - int x = m_Mobile.X, y = m_Mobile.Y; - Movement.Movement.Offset(d, ref x, ref y); - - var destroyables = 0; - - var eable = map.GetItemsInRange(new Point3D(x, y, m_Mobile.Location.Z), 1); - - foreach (var item in eable) - { - if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > m_Mobile.Z && - m_Mobile.Z + 16 > door.Z) - { - if (door.X != x || door.Y != y) - { - continue; - } - - if (!door.Locked || !door.UseLocks()) - { - m_Obstacles.Enqueue(door); - } - - if (!canDestroyObstacles) - { - break; - } - } - else if (canDestroyObstacles && item.Movable && item.ItemData.Impassable && - item.Z + item.ItemData.Height > m_Mobile.Z && m_Mobile.Z + 16 > item.Z) - { - if (!m_Mobile.InRange(item.GetWorldLocation(), 1)) - { - continue; - } - - m_Obstacles.Enqueue(item); - ++destroyables; - } - } - - eable.Free(); - - if (destroyables > 0) - { - Effects.PlaySound(new Point3D(x, y, m_Mobile.Z), m_Mobile.Map, 0x3B3); - } - - if (m_Obstacles.Count > 0) - { - blocked = false; // retry movement - } - - while (m_Obstacles.Count > 0) - { - var item = m_Obstacles.Dequeue(); - - if (item is BaseDoor door) - { - m_Mobile.DebugSay( - "Little do they expect, I've learned how to open doors. Didn't they read the script??" - ); - m_Mobile.DebugSay("*twist*"); - - door.Use(m_Mobile); - } - else - { - m_Mobile.DebugSay( - "Ugabooga. I'm so big and tough I can destroy it: {0}", - item.GetType().Name - ); - - if (item is Container cont) - { - for (var i = 0; i < cont.Items.Count; ++i) - { - var check = cont.Items[i]; - - if (check.Movable && check.ItemData.Impassable && - cont.Z + check.ItemData.Height > m_Mobile.Z) - { - m_Obstacles.Enqueue(check); - } - } - - cont.Destroy(); - } - else - { - item.Delete(); - } - } - } - - if (!blocked) - { - blocked = !m_Mobile.Move(d); - } - } - } - - if (blocked) - { - var offset = Utility.RandomDouble() >= 0.6 ? 1 : -1; - - for (var i = 0; i < 2; ++i) - { - m_Mobile.TurnInternal(offset); - - if (m_Mobile.Move(m_Mobile.Direction)) - { - MoveImpl.IgnoreMovableImpassables = false; - return MoveResult.SuccessAutoTurn; - } - } - - MoveImpl.IgnoreMovableImpassables = false; - return wasPushing ? MoveResult.BadState : MoveResult.Blocked; - } - - MoveImpl.IgnoreMovableImpassables = false; - return MoveResult.Success; - } - - public virtual void WalkRandomInHome(int iChanceToNotMove, int iChanceToDir, int iSteps) - { - if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves) - { - return; - } - - if (m_Mobile.Home == Point3D.Zero) - { - if (m_Mobile.Spawner is RegionSpawner rs) - { - Region region = rs.SpawnRegion; - - if (m_Mobile.Region.AcceptsSpawnsFrom(region)) - { - m_Mobile.WalkRegion = region; - WalkRandom(iChanceToNotMove, iChanceToDir, iSteps); - m_Mobile.WalkRegion = null; - } - else - { - if (region.GoLocation != Point3D.Zero && Utility.Random(10) > 5) - { - DoMove(m_Mobile.GetDirectionTo(region.GoLocation)); - } - else - { - WalkRandom(iChanceToNotMove, iChanceToDir, 1); - } - } - } - else - { - WalkRandom(iChanceToNotMove, iChanceToDir, iSteps); - } - } - else - { - for (var i = 0; i < iSteps; i++) - { - if (m_Mobile.RangeHome != 0) - { - var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m_Mobile.Home); - - if (iCurrDist < m_Mobile.RangeHome * 2 / 3) - { - WalkRandom(iChanceToNotMove, iChanceToDir, 1); - } - else if (iCurrDist > m_Mobile.RangeHome) - { - DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home)); - } - else - { - if (Utility.Random(10) > 5) - { - DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home)); - } - else - { - WalkRandom(iChanceToNotMove, iChanceToDir, 1); - } - } - } - else - { - if (m_Mobile.Location != m_Mobile.Home) - { - DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home)); - } - } - } - } - } - - public virtual bool CheckFlee() - { - if (m_Mobile.CheckFlee()) - { - var combatant = m_Mobile.Combatant; - - if (combatant == null) - { - WalkRandom(1, 2, 1); - } - else - { - var d = combatant.GetDirectionTo(m_Mobile); - - d = (Direction)((int)d + Utility.RandomMinMax(-1, +1)); - - m_Mobile.Direction = d; - m_Mobile.Move(d); - } - - return true; - } - - return false; - } - - public virtual void OnTeleported() - { - if (m_Path != null) + if (m_Mobile.Debug) { m_Mobile.DebugSay("Teleported; repathing"); - m_Path.ForceRepath(); } + + m_Path.ForceRepath(); } + } - public virtual bool MoveTo(Mobile m, bool run, int range) + public virtual bool MoveTo(Mobile m, bool run, int range) + { + if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves || m?.Deleted != false) { - if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves || m?.Deleted != false) - { - return false; - } - - if (m_Mobile.InRange(m, range)) - { - m_Path = null; - return true; - } - - if (m_Path?.Goal == m) - { - if (m_Path.Follow(run, 1)) - { - m_Path = null; - return true; - } - } - else if (!DoMove(m_Mobile.GetDirectionTo(m, run), true)) - { - m_Path = new PathFollower(m_Mobile, m) { Mover = DoMoveImpl }; - - if (m_Path.Follow(run, 1)) - { - m_Path = null; - return true; - } - } - else - { - m_Path = null; - return true; - } - return false; } - /* - * Walk at range distance from mobile - * - * iSteps : Number of steps - * bRun : Do we run - * iWantDistMin : The minimum distance we want to be - * iWantDistMax : The maximum distance we want to be - * - */ - public virtual bool WalkMobileRange(Mobile m, int iSteps, bool bRun, int iWantDistMin, int iWantDistMax) + if (m_Mobile.InRange(m, range)) { - if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves) + m_Path = null; + return true; + } + + if (m_Path?.Goal == m) + { + if (m_Path.Follow(run, 1)) { - return false; + m_Path = null; + return true; } + } + else if (!DoMove(m_Mobile.GetDirectionTo(m, run), true)) + { + m_Path = new PathFollower(m_Mobile, m) { Mover = DoMoveImpl }; - if (m == null) + if (m_Path.Follow(run, 1)) { - return false; + m_Path = null; + return true; } + } + else + { + m_Path = null; + return true; + } - for (var i = 0; i < iSteps; i++) + return false; + } + + /* + * Walk at range distance from mobile + * + * iSteps : Number of steps + * bRun : Do we run + * iWantDistMin : The minimum distance we want to be + * iWantDistMax : The maximum distance we want to be + * + */ + public virtual bool WalkMobileRange(Mobile m, int iSteps, bool bRun, int iWantDistMin, int iWantDistMax) + { + if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves) + { + return false; + } + + if (m == null) + { + return false; + } + + for (var i = 0; i < iSteps; i++) + { + // Get the current distance + var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m); + + if (iCurrDist < iWantDistMin || iCurrDist > iWantDistMax) { - // Get the current distance - var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(m); + var needCloser = iCurrDist > iWantDistMax; + var needFurther = !needCloser; - if (iCurrDist < iWantDistMin || iCurrDist > iWantDistMax) + if (needCloser && m_Path != null && m_Path.Goal == m) { - var needCloser = iCurrDist > iWantDistMax; - var needFurther = !needCloser; - - if (needCloser && m_Path != null && m_Path.Goal == m) + if (m_Path.Follow(bRun, 1)) { + m_Path = null; + } + } + else + { + var dirTo = iCurrDist > iWantDistMax ? + m_Mobile.GetDirectionTo(m, bRun) : m.GetDirectionTo(m_Mobile, bRun); + + if (!DoMove(dirTo, true) && needCloser) + { + m_Path = new PathFollower(m_Mobile, m) { Mover = DoMoveImpl }; + if (m_Path.Follow(bRun, 1)) { m_Path = null; @@ -2330,759 +2479,747 @@ namespace Server.Mobiles } else { - var dirTo = iCurrDist > iWantDistMax ? - m_Mobile.GetDirectionTo(m, bRun) : m.GetDirectionTo(m_Mobile, bRun); - - if (!DoMove(dirTo, true) && needCloser) - { - m_Path = new PathFollower(m_Mobile, m) { Mover = DoMoveImpl }; - - if (m_Path.Follow(bRun, 1)) - { - m_Path = null; - } - } - else - { - m_Path = null; - } + m_Path = null; } } - else - { - return true; - } } - - // Get the current distance - var iNewDist = (int)m_Mobile.GetDistanceToSqrt(m); - - return iNewDist >= iWantDistMin && iNewDist <= iWantDistMax; - } - - /* - * Here we check to acquire a target from our surrounding - * - * iRange : The range - * acqType : A type of acquire we want (closest, strongest, etc) - * bPlayerOnly : Don't bother with other creatures or NPCs, want a player - * bFacFriend : Check people in my faction - * bFacFoe : Check people in other factions - * - */ - public virtual bool AcquireFocusMob(int iRange, FightMode acqType, bool bPlayerOnly, bool bFacFriend, bool bFacFoe) - { - if (m_Mobile.Deleted) - { - return false; - } - - if (m_Mobile.BardProvoked) - { - if (m_Mobile.BardTarget?.Deleted != false) - { - m_Mobile.FocusMob = null; - return false; - } - - m_Mobile.FocusMob = m_Mobile.BardTarget; - return m_Mobile.FocusMob != null; - } - - if (m_Mobile.Controlled) - { - if (m_Mobile.ControlTarget?.Deleted != false || m_Mobile.ControlTarget.Hidden || - !m_Mobile.ControlTarget.Alive || m_Mobile.ControlTarget.IsDeadBondedPet || - !m_Mobile.InRange(m_Mobile.ControlTarget, m_Mobile.RangePerception * 2)) - { - if (m_Mobile.ControlTarget != null && m_Mobile.ControlTarget != m_Mobile.ControlMaster) - { - m_Mobile.ControlTarget = null; - } - - m_Mobile.FocusMob = null; - return false; - } - - m_Mobile.FocusMob = m_Mobile.ControlTarget; - return m_Mobile.FocusMob != null; - } - - if (m_Mobile.ConstantFocus != null) - { - m_Mobile.DebugSay("Acquired my constant focus"); - m_Mobile.FocusMob = m_Mobile.ConstantFocus; - return true; - } - - if (acqType == FightMode.None) - { - m_Mobile.FocusMob = null; - return false; - } - - if (acqType == FightMode.Aggressor && m_Mobile.Aggressors.Count == 0 && m_Mobile.Aggressed.Count == 0 && - m_Mobile.FactionAllegiance == null && m_Mobile.EthicAllegiance == null) - { - m_Mobile.FocusMob = null; - return false; - } - - if (Core.TickCount - m_Mobile.NextReacquireTime < 0) - { - m_Mobile.FocusMob = null; - return false; - } - - m_Mobile.NextReacquireTime = Core.TickCount + (int)m_Mobile.ReacquireDelay.TotalMilliseconds; - - m_Mobile.DebugSay("Acquiring..."); - - var map = m_Mobile.Map; - - if (map != null) - { - Mobile newFocusMob = null; - var val = double.MinValue; - - var eable = map.GetMobilesInRange(m_Mobile.Location, iRange); - - foreach (var m in eable) - { - if (m.Deleted || m.Blessed) - { - continue; - } - - // Let's not target ourselves... - if (m == m_Mobile || m is BaseFamiliar) - { - continue; - } - - // Dead targets are invalid. - if (!m.Alive || m.IsDeadBondedPet) - { - continue; - } - - // Staff members cannot be targeted. - if (m.AccessLevel > AccessLevel.Player) - { - continue; - } - - // Does it have to be a player? - if (bPlayerOnly && !m.Player) - { - continue; - } - - // Can't acquire a target we can't see. - if (!m_Mobile.CanSee(m)) - { - continue; - } - - var bc = m as BaseCreature; - var pm = m as PlayerMobile; - - if (Core.AOS && bc?.Summoned == true && bc?.Controlled != true) - { - continue; - } - - if (m_Mobile.Summoned && m_Mobile.SummonMaster != null) - { - // If this is a summon, it can't target its controller. - if (m == m_Mobile.SummonMaster) - { - continue; - } - - // It also must abide by harmful spell rules. - if (!SpellHelper.ValidIndirectTarget(m_Mobile.SummonMaster, m)) - { - continue; - } - - // Animated creatures cannot attack players directly. - if (pm != null && m_Mobile.IsAnimatedDead) - { - continue; - } - } - - // If we only want faction friends, make sure it's one. - if (bFacFriend && !m_Mobile.IsFriend(m)) - { - continue; - } - - // Ignore anyone under EtherealVoyage - if (TransformationSpellHelper.UnderTransformation(m, typeof(EtherealVoyageSpell))) - { - continue; - } - - // Ignore players with activated honor - if (pm?.HonorActive == true && m_Mobile.Combatant != m) - { - continue; - } - - if (acqType is FightMode.Aggressor or FightMode.Evil) - { - var bValid = IsHostile(m); - - if (!bValid) - { - bValid = m_Mobile.GetFactionAllegiance(m) == BaseCreature.Allegiance.Enemy || - m_Mobile.GetEthicAllegiance(m) == BaseCreature.Allegiance.Enemy; - } - - if (acqType == FightMode.Evil && !bValid) - { - if (bc?.Controlled == true && bc?.ControlMaster != null) - { - bValid = bc.ControlMaster.Karma < 0; - } - else - { - bValid = m.Karma < 0; - } - } - - if (!bValid) - { - continue; - } - } - else - { - // Same goes for faction enemies. - if (bFacFoe && !m_Mobile.IsEnemy(m)) - { - continue; - } - - // If it's an enemy factioned mobile, make sure we can be harmful to it. - if (bFacFoe && !bFacFriend && !m_Mobile.CanBeHarmful(m, false)) - { - continue; - } - } - - var theirVal = m_Mobile.GetFightModeRanking(m, acqType, bPlayerOnly); - - if (theirVal > val && m_Mobile.InLOS(m)) - { - newFocusMob = m; - val = theirVal; - } - } - - eable.Free(); - - m_Mobile.FocusMob = newFocusMob; - } - - return m_Mobile.FocusMob != null; - } - - private bool IsHostile(Mobile from) - { - var count = Math.Max(m_Mobile.Aggressors.Count, m_Mobile.Aggressed.Count); - - if (m_Mobile.Combatant == from || from.Combatant == m_Mobile) + else { return true; } + } - if (count > 0) - { - for (var a = 0; a < count; ++a) - { - if (a < m_Mobile.Aggressed.Count && m_Mobile.Aggressed[a].Attacker == from) - { - return true; - } + // Get the current distance + var iNewDist = (int)m_Mobile.GetDistanceToSqrt(m); - if (a < m_Mobile.Aggressors.Count && m_Mobile.Aggressors[a].Defender == from) - { - return true; - } - } - } + return iNewDist >= iWantDistMin && iNewDist <= iWantDistMax; + } + /* + * Here we check to acquire a target from our surrounding + * + * iRange : The range + * acqType : A type of acquire we want (closest, strongest, etc) + * bPlayerOnly : Don't bother with other creatures or NPCs, want a player + * bFacFriend : Check people in my faction + * bFacFoe : Check people in other factions + * + */ + public virtual bool AcquireFocusMob(int iRange, FightMode acqType, bool bPlayerOnly, bool bFacFriend, bool bFacFoe) + { + if (m_Mobile.Deleted) + { return false; } - public virtual void DetectHidden() + if (m_Mobile.BardProvoked) { - if (m_Mobile.Deleted || m_Mobile.Map == null) + if (m_Mobile.BardTarget?.Deleted != false) { - return; + m_Mobile.FocusMob = null; + return false; } - m_Mobile.DebugSay("Checking for hidden players"); + m_Mobile.FocusMob = m_Mobile.BardTarget; + return m_Mobile.FocusMob != null; + } - var srcSkill = m_Mobile.Skills.DetectHidden.Value; - - if (srcSkill <= 0) + if (m_Mobile.Controlled) + { + if (m_Mobile.ControlTarget?.Deleted != false || m_Mobile.ControlTarget.Hidden || + !m_Mobile.ControlTarget.Alive || m_Mobile.ControlTarget.IsDeadBondedPet || + !m_Mobile.InRange(m_Mobile.ControlTarget, m_Mobile.RangePerception * 2)) { - return; - } - - var eable = m_Mobile.GetMobilesInRange(m_Mobile.RangePerception); - - foreach (var trg in eable) - { - if (trg != m_Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player && - m_Mobile.InLOS(trg)) + if (m_Mobile.ControlTarget != null && m_Mobile.ControlTarget != m_Mobile.ControlMaster) { - m_Mobile.DebugSay("Trying to detect {0}", trg.Name); + m_Mobile.ControlTarget = null; + } - var trgHiding = trg.Skills.Hiding.Value / 2.9; - var trgStealth = trg.Skills.Stealth.Value / 1.8; + m_Mobile.FocusMob = null; + return false; + } - var chance = srcSkill / 1.2 - Math.Min(trgHiding, trgStealth); + m_Mobile.FocusMob = m_Mobile.ControlTarget; + return m_Mobile.FocusMob != null; + } - if (chance < srcSkill / 10) + if (m_Mobile.ConstantFocus != null) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Acquired my constant focus"); + } + + m_Mobile.FocusMob = m_Mobile.ConstantFocus; + return true; + } + + if (acqType == FightMode.None) + { + m_Mobile.FocusMob = null; + return false; + } + + if (acqType == FightMode.Aggressor && m_Mobile.Aggressors.Count == 0 && m_Mobile.Aggressed.Count == 0 && + m_Mobile.FactionAllegiance == null && m_Mobile.EthicAllegiance == null) + { + m_Mobile.FocusMob = null; + return false; + } + + if (Core.TickCount - m_Mobile.NextReacquireTime < 0) + { + m_Mobile.FocusMob = null; + return false; + } + + m_Mobile.NextReacquireTime = Core.TickCount + (int)m_Mobile.ReacquireDelay.TotalMilliseconds; + + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Acquiring..."); + } + + var map = m_Mobile.Map; + + if (map == null) + { + // TODO: Is this correct? Maybe it should return false? + return m_Mobile.FocusMob != null; + } + + Mobile newFocusMob = null; + var val = double.MinValue; + + var eable = map.GetMobilesInRange(m_Mobile.Location, iRange); + + foreach (var m in eable) + { + if (m.Deleted || m.Blessed) + { + continue; + } + + // Let's not target ourselves... + if (m == m_Mobile || m is BaseFamiliar) + { + continue; + } + + // Dead targets are invalid. + if (!m.Alive || m.IsDeadBondedPet) + { + continue; + } + + // Staff members cannot be targeted. + if (m.AccessLevel > AccessLevel.Player) + { + continue; + } + + // Does it have to be a player? + if (bPlayerOnly && !m.Player) + { + continue; + } + + // Can't acquire a target we can't see. + if (!m_Mobile.CanSee(m)) + { + continue; + } + + var bc = m as BaseCreature; + var pm = m as PlayerMobile; + + if (Core.AOS && bc?.Summoned == true && bc?.Controlled != true) + { + continue; + } + + if (m_Mobile.Summoned && m_Mobile.SummonMaster != null) + { + // If this is a summon, it can't target its controller. + if (m == m_Mobile.SummonMaster) + { + continue; + } + + // It also must abide by harmful spell rules. + if (!SpellHelper.ValidIndirectTarget(m_Mobile.SummonMaster, m)) + { + continue; + } + + // Animated creatures cannot attack players directly. + if (pm != null && m_Mobile.IsAnimatedDead) + { + continue; + } + } + + // If we only want faction friends, make sure it's one. + if (bFacFriend && !m_Mobile.IsFriend(m)) + { + continue; + } + + // Ignore anyone under EtherealVoyage + if (TransformationSpellHelper.UnderTransformation(m, typeof(EtherealVoyageSpell))) + { + continue; + } + + // Ignore players with activated honor + if (pm?.HonorActive == true && m_Mobile.Combatant != m) + { + continue; + } + + if (acqType is FightMode.Aggressor or FightMode.Evil) + { + var bValid = IsHostile(m); + + if (!bValid) + { + bValid = m_Mobile.GetFactionAllegiance(m) == BaseCreature.Allegiance.Enemy || + m_Mobile.GetEthicAllegiance(m) == BaseCreature.Allegiance.Enemy; + } + + if (acqType == FightMode.Evil && !bValid) + { + if (bc?.Controlled == true && bc?.ControlMaster != null) { - chance = srcSkill / 10; - } - - chance /= 100; - - if (chance > Utility.RandomDouble()) - { - trg.RevealingAction(); - trg.SendLocalizedMessage(500814); // You have been revealed! - } - } - } - - eable.Free(); - } - - public virtual void Deactivate() - { - if (m_Mobile.PlayerRangeSensitive) - { - m_Timer.Stop(); - - var spawner = m_Mobile.Spawner; - - if (spawner?.ReturnOnDeactivate == true && !m_Mobile.Controlled && ( - spawner.HomeLocation == Point3D.Zero && !m_Mobile.Region.AcceptsSpawnsFrom(spawner.Region) || - !m_Mobile.InRange(spawner.HomeLocation, spawner.HomeRange) - )) - { - Timer.StartTimer(ReturnToHome); - } - } - } - - private void ReturnToHome() - { - if (m_Mobile.Spawner != null) - { - var loc = m_Mobile.Spawner.GetSpawnPosition(m_Mobile, m_Mobile.Spawner.Map); - - if (loc != Point3D.Zero) - { - m_Mobile.MoveToWorld(loc, m_Mobile.Spawner.Map); - } - } - } - - public virtual void Activate() - { - if (!m_Timer.Running) - { - // We want to randomize the time at which the AI activates. - // This triggers when a mob is first created since it moves from the internal map to it's added location - // If we spawn lots of mobs, we don't want their AI synchronized exactly. - m_Timer.Delay = TimeSpan.FromMilliseconds(Utility.Random(48) * 8); - m_Timer.Start(); - } - } - - /* - * The mobile changed speeds, we must adjust the timer - */ - public virtual void OnCurrentSpeedChanged() - { - m_Timer.Interval = TimeSpan.FromSeconds(Math.Max(0.008, m_Mobile.CurrentSpeed)); - } - - private class InternalEntry : ContextMenuEntry - { - private readonly BaseAI m_AI; - private readonly Mobile m_From; - private readonly BaseCreature m_Mobile; - private readonly OrderType m_Order; - - public InternalEntry(Mobile from, int number, int range, BaseCreature mobile, BaseAI ai, OrderType order) - : base(number, range) - { - m_From = from; - m_Mobile = mobile; - m_AI = ai; - m_Order = order; - - if (mobile.IsDeadPet && order is OrderType.Guard or OrderType.Attack or OrderType.Transfer or OrderType.Drop) - { - Enabled = false; - } - } - - public override void OnClick() - { - if (m_Mobile.Deleted || !m_Mobile.Controlled || !m_From.CheckAlive()) - { - return; - } - - if (m_Mobile.IsDeadPet && m_Order is OrderType.Guard or OrderType.Attack or OrderType.Transfer or OrderType.Drop) - { - return; - } - - var isOwner = m_From == m_Mobile.ControlMaster; - var isFriend = !isOwner && m_Mobile.IsPetFriend(m_From); - - if (!isOwner && !isFriend) - { - return; - } - - if (isFriend && m_Order != OrderType.Follow && m_Order != OrderType.Stay && m_Order != OrderType.Stop) - { - return; - } - - switch (m_Order) - { - case OrderType.Follow: - case OrderType.Attack: - case OrderType.Transfer: - case OrderType.Friend: - case OrderType.Unfriend: - { - if (m_Order == OrderType.Transfer && m_From.HasTrade) - { - m_From.SendLocalizedMessage(1010507); // You cannot transfer a pet with a trade pending - } - else if (m_Order == OrderType.Friend && m_From.HasTrade) - { - m_From.SendLocalizedMessage(1070947); // You cannot friend a pet with a trade pending - } - else - { - m_AI.BeginPickTarget(m_From, m_Order); - } - - break; - } - case OrderType.Release: - { - if (m_Mobile.Summoned) - { - goto default; - } - - m_From.SendGump(new ConfirmReleaseGump(m_From, m_Mobile)); - - break; - } - default: - { - if (m_Mobile.CheckControlChance(m_From)) - { - m_Mobile.ControlOrder = m_Order; - } - - break; - } - } - } - } - - private class TransferItem : Item - { - private readonly BaseCreature m_Creature; - - public TransferItem(BaseCreature creature) - : base(ShrinkTable.Lookup(creature)) - { - m_Creature = creature; - - Movable = false; - - if (!Core.AOS) - { - Name = creature.Name; - } - else if (ItemID == ShrinkTable.DefaultItemID || - creature.GetType().IsDefined(typeof(FriendlyNameAttribute), false) || creature is Reptalon) - { - Name = FriendlyNameAttribute.GetFriendlyNameFor(creature.GetType()).ToString(); - } - - // (As Per OSI)No name. Normally, set by the ItemID of the Shrink Item unless we either explicitly set it with an Attribute, or, no lookup found - - Hue = creature.Hue & 0x0FFF; - } - - public TransferItem(Serial serial) - : base(serial) - { - } - - public static bool IsInCombat(BaseCreature creature) => - creature != null && (creature.Aggressors.Count > 0 || creature.Aggressed.Count > 0); - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - Delete(); - } - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - list.Add(1041603); // This item represents a pet currently in consideration for trade - list.Add(1041601, m_Creature.Name); // Pet Name: ~1_val~ - - if (m_Creature.ControlMaster != null) - { - list.Add(1041602, m_Creature.ControlMaster.Name); // Owner: ~1_val~ - } - } - - public override bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) - { - if (!base.AllowSecureTrade(from, to, newOwner, accepted)) - { - return false; - } - - if (Deleted || m_Creature?.Deleted != false || m_Creature.ControlMaster != from || - !from.CheckAlive() || !to.CheckAlive()) - { - return false; - } - - if (from.Map != m_Creature.Map || !from.InRange(m_Creature, 14)) - { - return false; - } - - var youngFrom = from is PlayerMobile mobile && mobile.Young; - var youngTo = to is PlayerMobile playerMobile && playerMobile.Young; - - if (accepted && youngFrom && !youngTo) - { - from.SendLocalizedMessage(502051); // As a young player, you may not transfer pets to older players. - } - else if (accepted && !youngFrom && youngTo) - { - from.SendLocalizedMessage(502052); // As an older player, you may not transfer pets to young players. - } - else if (accepted && !m_Creature.CanBeControlledBy(to)) - { - var args = $"{to.Name}\t{from.Name}\t "; - - // The pet refuses to be transferred because it will not obey ~1_NAME~.~3_BLANK~ - from.SendLocalizedMessage(1043248, args); - // The pet will not accept you as a master because it does not trust you.~3_BLANK~ - to.SendLocalizedMessage(1043249, args); - - return false; - } - else if (accepted && !m_Creature.CanBeControlledBy(from)) - { - var args = $"{to.Name}\t{from.Name}\t "; - - // The pet refuses to be transferred because it will not obey you sufficiently.~3_BLANK~ - from.SendLocalizedMessage(1043250, args); - // The pet will not accept you as a master because it does not trust ~2_NAME~.~3_BLANK~ - to.SendLocalizedMessage(1043251, args); - } - else if (accepted && to.Followers + m_Creature.ControlSlots > to.FollowersMax) - { - to.SendLocalizedMessage(1049607); // You have too many followers to control that creature. - - return false; - } - else if (accepted && IsInCombat(m_Creature)) - { - from.SendMessage("You may not transfer a pet that has recently been in combat."); - to.SendMessage("The pet may not be transferred to you because it has recently been in combat."); - - return false; - } - - return true; - } - - public override void OnSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) - { - if (Deleted) - { - return; - } - - Delete(); - - if (m_Creature?.Deleted != false || m_Creature.ControlMaster != from || !from.CheckAlive() || - !to.CheckAlive()) - { - return; - } - - if (from.Map != m_Creature.Map || !from.InRange(m_Creature, 14)) - { - return; - } - - if (accepted) - { - if (m_Creature.SetControlMaster(to)) - { - if (m_Creature.Summoned) - { - m_Creature.SummonMaster = to; - } - - m_Creature.ControlTarget = to; - m_Creature.ControlOrder = OrderType.Follow; - - m_Creature.BondingBegin = DateTime.MinValue; - m_Creature.OwnerAbandonTime = DateTime.MinValue; - m_Creature.IsBonded = false; - - m_Creature.PlaySound(m_Creature.GetIdleSound()); - - var args = $"{from.Name}\t{m_Creature.Name}\t{to.Name}"; - - from.SendLocalizedMessage(1043253, args); // You have transferred your pet to ~3_GETTER~. - // ~1_NAME~ has transferred the allegiance of ~2_PET_NAME~ to you. - to.SendLocalizedMessage(1043252, args); - } - } - } - } - - /* - * The Timer object - */ - private class AITimer : Timer - { - private readonly BaseAI m_Owner; - - public AITimer(BaseAI owner) : base( - TimeSpan.FromMilliseconds(Utility.Random(96) * 8), - TimeSpan.FromSeconds(Math.Max(0.0, owner.m_Mobile.CurrentSpeed)) - ) - { - m_Owner = owner; - m_Owner.m_NextDetectHidden = Core.TickCount; - } - - protected override void OnTick() - { - if (m_Owner.m_Mobile.Deleted) - { - Stop(); - return; - } - - if (m_Owner.m_Mobile.Map == null || m_Owner.m_Mobile.Map == Map.Internal) - { - m_Owner.Deactivate(); - return; - } - - if (m_Owner.m_Mobile.PlayerRangeSensitive) // have to check this in the timer.... - { - var sect = m_Owner.m_Mobile.Map.GetSector(m_Owner.m_Mobile.Location); - if (!sect.Active) - { - m_Owner.Deactivate(); - return; - } - } - - m_Owner.m_Mobile.OnThink(); - - if (m_Owner.m_Mobile.Deleted) - { - Stop(); - return; - } - - if (m_Owner.m_Mobile.Map == null || m_Owner.m_Mobile.Map == Map.Internal) - { - m_Owner.Deactivate(); - return; - } - - if (m_Owner.m_Mobile.BardPacified) - { - m_Owner.DoBardPacified(); - } - else if (m_Owner.m_Mobile.BardProvoked) - { - m_Owner.DoBardProvoked(); - } - else - { - if (!m_Owner.m_Mobile.Controlled) - { - if (!m_Owner.Think()) - { - Stop(); - return; - } + bValid = bc.ControlMaster.Karma < 0; } else { - if (!m_Owner.Obey()) - { - Stop(); - return; - } + bValid = m.Karma < 0; } } - if (m_Owner.CanDetectHidden && Core.TickCount - m_Owner.m_NextDetectHidden >= 0) + if (!bValid) { - m_Owner.DetectHidden(); + continue; + } + } + else + { + // Same goes for faction enemies. + if (bFacFoe && !m_Mobile.IsEnemy(m)) + { + continue; + } - // Not exactly OSI style, approximation. - var delay = Math.Min(15000 / m_Owner.m_Mobile.Int, 60); + // If it's an enemy factioned mobile, make sure we can be harmful to it. + if (bFacFoe && !bFacFriend && !m_Mobile.CanBeHarmful(m, false)) + { + continue; + } + } - var min = delay * (9 / 10); // 13s at 1000 int, 33s at 400 int, 54s at <250 int - var max = delay * (10 / 9); // 16s at 1000 int, 41s at 400 int, 66s at <250 int + var theirVal = m_Mobile.GetFightModeRanking(m, acqType, bPlayerOnly); - m_Owner.m_NextDetectHidden = Core.TickCount + - (int)TimeSpan.FromSeconds(Utility.RandomMinMax(min, max)).TotalMilliseconds; + if (theirVal > val && m_Mobile.InLOS(m)) + { + newFocusMob = m; + val = theirVal; + } + } + + eable.Free(); + + m_Mobile.FocusMob = newFocusMob; + return m_Mobile.FocusMob != null; + } + + private bool IsHostile(Mobile from) + { + var count = Math.Max(m_Mobile.Aggressors.Count, m_Mobile.Aggressed.Count); + + if (count <= 0 || m_Mobile.Combatant == from || from.Combatant == m_Mobile) + { + return true; + } + + for (var a = 0; a < count; ++a) + { + if (a < m_Mobile.Aggressed.Count && m_Mobile.Aggressed[a].Attacker == from) + { + return true; + } + + if (a < m_Mobile.Aggressors.Count && m_Mobile.Aggressors[a].Defender == from) + { + return true; + } + } + + return false; + } + + public virtual void DetectHidden() + { + if (m_Mobile.Deleted || m_Mobile.Map == null) + { + return; + } + + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Checking for hidden players"); + } + + var srcSkill = m_Mobile.Skills.DetectHidden.Value; + + if (srcSkill <= 0) + { + return; + } + + var eable = m_Mobile.GetMobilesInRange(m_Mobile.RangePerception); + + foreach (var trg in eable) + { + if (trg != m_Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player && + m_Mobile.InLOS(trg)) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"Trying to detect {trg.Name}"); + } + + var trgHiding = trg.Skills.Hiding.Value / 2.9; + var trgStealth = trg.Skills.Stealth.Value / 1.8; + + var chance = srcSkill / 1.2 - Math.Min(trgHiding, trgStealth); + + if (chance < srcSkill / 10) + { + chance = srcSkill / 10; + } + + chance /= 100; + + if (chance > Utility.RandomDouble()) + { + trg.RevealingAction(); + trg.SendLocalizedMessage(500814); // You have been revealed! } } } + + eable.Free(); } -} + + public virtual void Deactivate() + { + if (m_Mobile.PlayerRangeSensitive) + { + m_Timer.Stop(); + + var spawner = m_Mobile.Spawner; + + if (spawner?.ReturnOnDeactivate == true && !m_Mobile.Controlled && ( + spawner.HomeLocation == Point3D.Zero && !m_Mobile.Region.AcceptsSpawnsFrom(spawner.Region) || + !m_Mobile.InRange(spawner.HomeLocation, spawner.HomeRange) + )) + { + Timer.StartTimer(ReturnToHome); + } + } + } + + private void ReturnToHome() + { + if (m_Mobile.Spawner != null) + { + var loc = m_Mobile.Spawner.GetSpawnPosition(m_Mobile, m_Mobile.Spawner.Map); + + if (loc != Point3D.Zero) + { + m_Mobile.MoveToWorld(loc, m_Mobile.Spawner.Map); + } + } + } + + public virtual void Activate() + { + if (!m_Timer.Running) + { + // We want to randomize the time at which the AI activates. + // This triggers when a mob is first created since it moves from the internal map to it's added location + // If we spawn lots of mobs, we don't want their AI synchronized exactly. + m_Timer.Delay = TimeSpan.FromMilliseconds(Utility.Random(48) * 8); + m_Timer.Start(); + } + } + + /* + * The mobile changed speeds, we must adjust the timer + */ + public virtual void OnCurrentSpeedChanged() + { + m_Timer.Interval = TimeSpan.FromSeconds(Math.Max(0.008, m_Mobile.CurrentSpeed)); + } + + private class InternalEntry : ContextMenuEntry + { + private readonly BaseAI m_AI; + private readonly Mobile m_From; + private readonly BaseCreature m_Mobile; + private readonly OrderType m_Order; + + public InternalEntry(Mobile from, int number, int range, BaseCreature mobile, BaseAI ai, OrderType order) + : base(number, range) + { + m_From = from; + m_Mobile = mobile; + m_AI = ai; + m_Order = order; + + if (mobile.IsDeadPet && order is OrderType.Guard or OrderType.Attack or OrderType.Transfer or OrderType.Drop) + { + Enabled = false; + } + } + + public override void OnClick() + { + if (m_Mobile.Deleted || !m_Mobile.Controlled || !m_From.CheckAlive()) + { + return; + } + + if (m_Mobile.IsDeadPet && m_Order is OrderType.Guard or OrderType.Attack or OrderType.Transfer or OrderType.Drop) + { + return; + } + + var isOwner = m_From == m_Mobile.ControlMaster; + var isFriend = !isOwner && m_Mobile.IsPetFriend(m_From); + + if (!isOwner && !isFriend) + { + return; + } + + if (isFriend && m_Order != OrderType.Follow && m_Order != OrderType.Stay && m_Order != OrderType.Stop) + { + return; + } + + switch (m_Order) + { + case OrderType.Follow: + case OrderType.Attack: + case OrderType.Transfer: + case OrderType.Friend: + case OrderType.Unfriend: + { + if (m_Order == OrderType.Transfer && m_From.HasTrade) + { + m_From.SendLocalizedMessage(1010507); // You cannot transfer a pet with a trade pending + } + else if (m_Order == OrderType.Friend && m_From.HasTrade) + { + m_From.SendLocalizedMessage(1070947); // You cannot friend a pet with a trade pending + } + else + { + m_AI.BeginPickTarget(m_From, m_Order); + } + + break; + } + case OrderType.Release: + { + if (m_Mobile.Summoned) + { + goto default; + } + + m_From.SendGump(new ConfirmReleaseGump(m_From, m_Mobile)); + + break; + } + default: + { + if (m_Mobile.CheckControlChance(m_From)) + { + m_Mobile.ControlOrder = m_Order; + } + + break; + } + } + } + } + + private class TransferItem : Item + { + private readonly BaseCreature m_Creature; + + public TransferItem(BaseCreature creature) + : base(ShrinkTable.Lookup(creature)) + { + m_Creature = creature; + + Movable = false; + + if (!Core.AOS) + { + Name = creature.Name; + } + else if (ItemID == ShrinkTable.DefaultItemID || + creature.GetType().IsDefined(typeof(FriendlyNameAttribute), false) || creature is Reptalon) + { + Name = FriendlyNameAttribute.GetFriendlyNameFor(creature.GetType()).ToString(); + } + + // (As Per OSI)No name. Normally, set by the ItemID of the Shrink Item unless we either explicitly set it with an Attribute, or, no lookup found + + Hue = creature.Hue & 0x0FFF; + } + + public TransferItem(Serial serial) + : base(serial) + { + } + + public static bool IsInCombat(BaseCreature creature) => + creature != null && (creature.Aggressors.Count > 0 || creature.Aggressed.Count > 0); + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + Delete(); + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + list.Add(1041603); // This item represents a pet currently in consideration for trade + list.Add(1041601, m_Creature.Name); // Pet Name: ~1_val~ + + if (m_Creature.ControlMaster != null) + { + list.Add(1041602, m_Creature.ControlMaster.Name); // Owner: ~1_val~ + } + } + + public override bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) + { + if (!base.AllowSecureTrade(from, to, newOwner, accepted)) + { + return false; + } + + if (Deleted || m_Creature?.Deleted != false || m_Creature.ControlMaster != from || + !from.CheckAlive() || !to.CheckAlive()) + { + return false; + } + + if (from.Map != m_Creature.Map || !from.InRange(m_Creature, 14)) + { + return false; + } + + var youngFrom = from is PlayerMobile mobile && mobile.Young; + var youngTo = to is PlayerMobile playerMobile && playerMobile.Young; + + if (accepted && youngFrom && !youngTo) + { + from.SendLocalizedMessage(502051); // As a young player, you may not transfer pets to older players. + } + else if (accepted && !youngFrom && youngTo) + { + from.SendLocalizedMessage(502052); // As an older player, you may not transfer pets to young players. + } + else if (accepted && !m_Creature.CanBeControlledBy(to)) + { + var args = $"{to.Name}\t{from.Name}\t "; + + // The pet refuses to be transferred because it will not obey ~1_NAME~.~3_BLANK~ + from.SendLocalizedMessage(1043248, args); + // The pet will not accept you as a master because it does not trust you.~3_BLANK~ + to.SendLocalizedMessage(1043249, args); + + return false; + } + else if (accepted && !m_Creature.CanBeControlledBy(from)) + { + var args = $"{to.Name}\t{from.Name}\t "; + + // The pet refuses to be transferred because it will not obey you sufficiently.~3_BLANK~ + from.SendLocalizedMessage(1043250, args); + // The pet will not accept you as a master because it does not trust ~2_NAME~.~3_BLANK~ + to.SendLocalizedMessage(1043251, args); + } + else if (accepted && to.Followers + m_Creature.ControlSlots > to.FollowersMax) + { + to.SendLocalizedMessage(1049607); // You have too many followers to control that creature. + + return false; + } + else if (accepted && IsInCombat(m_Creature)) + { + from.SendMessage("You may not transfer a pet that has recently been in combat."); + to.SendMessage("The pet may not be transferred to you because it has recently been in combat."); + + return false; + } + + return true; + } + + public override void OnSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) + { + if (Deleted) + { + return; + } + + Delete(); + + if (m_Creature?.Deleted != false || m_Creature.ControlMaster != from || !from.CheckAlive() || + !to.CheckAlive()) + { + return; + } + + if (from.Map != m_Creature.Map || !from.InRange(m_Creature, 14)) + { + return; + } + + if (accepted && m_Creature.SetControlMaster(to)) + { + if (m_Creature.Summoned) + { + m_Creature.SummonMaster = to; + } + + m_Creature.ControlTarget = to; + m_Creature.ControlOrder = OrderType.Follow; + + m_Creature.BondingBegin = DateTime.MinValue; + m_Creature.OwnerAbandonTime = DateTime.MinValue; + m_Creature.IsBonded = false; + + m_Creature.PlaySound(m_Creature.GetIdleSound()); + + var args = $"{from.Name}\t{m_Creature.Name}\t{to.Name}"; + + from.SendLocalizedMessage(1043253, args); // You have transferred your pet to ~3_GETTER~. + // ~1_NAME~ has transferred the allegiance of ~2_PET_NAME~ to you. + to.SendLocalizedMessage(1043252, args); + } + } + } + + /* + * The Timer object + */ + private class AITimer : Timer + { + private readonly BaseAI m_Owner; + + public AITimer(BaseAI owner) : base( + TimeSpan.FromMilliseconds(Utility.Random(96) * 8), + TimeSpan.FromSeconds(Math.Max(0.0, owner.m_Mobile.CurrentSpeed)) + ) + { + m_Owner = owner; + m_Owner.m_NextDetectHidden = Core.TickCount; + } + + protected override void OnTick() + { + if (m_Owner.m_Mobile.Deleted) + { + Stop(); + return; + } + + if (m_Owner.m_Mobile.Map == null || m_Owner.m_Mobile.Map == Map.Internal) + { + m_Owner.Deactivate(); + return; + } + + if (m_Owner.m_Mobile.PlayerRangeSensitive) // have to check this in the timer.... + { + var sect = m_Owner.m_Mobile.Map.GetSector(m_Owner.m_Mobile.Location); + if (!sect.Active) + { + m_Owner.Deactivate(); + return; + } + } + + m_Owner.m_Mobile.OnThink(); + + if (m_Owner.m_Mobile.Deleted) + { + Stop(); + return; + } + + if (m_Owner.m_Mobile.Map == null || m_Owner.m_Mobile.Map == Map.Internal) + { + m_Owner.Deactivate(); + return; + } + + if (m_Owner.m_Mobile.BardPacified) + { + m_Owner.DoBardPacified(); + } + else if (m_Owner.m_Mobile.BardProvoked) + { + m_Owner.DoBardProvoked(); + } + else if (!m_Owner.m_Mobile.Controlled) + { + if (!m_Owner.Think()) + { + Stop(); + return; + } + } + else if (!m_Owner.Obey()) + { + Stop(); + return; + } + + if (m_Owner.CanDetectHidden && Core.TickCount - m_Owner.m_NextDetectHidden >= 0) + { + m_Owner.DetectHidden(); + + // Not exactly OSI style, approximation. + var delay = Math.Min(15000 / m_Owner.m_Mobile.Int, 60); + + var min = delay * (9 / 10); // 13s at 1000 int, 33s at 400 int, 54s at <250 int + var max = delay * (10 / 9); // 16s at 1000 int, 41s at 400 int, 66s at <250 int + + m_Owner.m_NextDetectHidden = Core.TickCount + + (int)TimeSpan.FromSeconds(Utility.RandomMinMax(min, max)).TotalMilliseconds; + } + } + } +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/BerserkAI.cs b/Projects/UOContent/Mobiles/AI/BerserkAI.cs index f1f64f23e..d43449938 100644 --- a/Projects/UOContent/Mobiles/AI/BerserkAI.cs +++ b/Projects/UOContent/Mobiles/AI/BerserkAI.cs @@ -1,91 +1,97 @@ -namespace Server.Mobiles +namespace Server.Mobiles; + +public class BerserkAI : BaseAI { - public class BerserkAI : BaseAI + public BerserkAI(BaseCreature m) : base(m) { - public BerserkAI(BaseCreature m) : base(m) + } + + public override bool DoActionWander() + { + if (m_Mobile.Debug) { + m_Mobile.DebugSay("I have no combatant"); } - public override bool DoActionWander() + if (AcquireFocusMob(m_Mobile.RangePerception, FightMode.Closest, false, true, true)) { - m_Mobile.DebugSay("I have No Combatant"); - - if (AcquireFocusMob(m_Mobile.RangePerception, FightMode.Closest, false, true, true)) + if (m_Mobile.Debug) { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name} and I will attack"); - } - - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else - { - base.DoActionWander(); + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name} and I will attack"); } + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionWander(); + } + + return true; + } + + public override bool DoActionCombat() + { + if (m_Mobile.Combatant?.Deleted != false) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("My combatant is deleted"); + } + + Action = ActionType.Guard; return true; } - public override bool DoActionCombat() + if ( + m_Mobile.Combatant != null && + !WalkMobileRange( + m_Mobile.Combatant, + 1, + true, + m_Mobile.RangeFight, + m_Mobile.RangeFight + ) + ) { - if (m_Mobile.Combatant?.Deleted != false) + if (m_Mobile.Debug) { - m_Mobile.DebugSay("My combatant is deleted"); + m_Mobile.DebugSay($"I am still not in range of {m_Mobile.Combatant.Name}"); + } + + if ((int)m_Mobile.GetDistanceToSqrt(m_Mobile.Combatant) > m_Mobile.RangePerception + 1) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I have lost {m_Mobile.Combatant.Name}"); + } + Action = ActionType.Guard; return true; } - - if ( - m_Mobile.Combatant != null && - !WalkMobileRange( - m_Mobile.Combatant, - 1, - true, - m_Mobile.RangeFight, - m_Mobile.RangeFight - ) - ) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay($"I am still not in range of {m_Mobile.Combatant.Name}"); - } - - if ((int)m_Mobile.GetDistanceToSqrt(m_Mobile.Combatant) > m_Mobile.RangePerception + 1) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay($"I have lost {m_Mobile.Combatant.Name}"); - } - - Action = ActionType.Guard; - return true; - } - } - - return true; } - public override bool DoActionGuard() - { - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, true, true)) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I have detected {0}, attacking", m_Mobile.FocusMob.Name); - } - - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else - { - base.DoActionGuard(); - } - - return true; - } + return true; } -} + + public override bool DoActionGuard() + { + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, true, true)) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name}, attacking"); + } + + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionGuard(); + } + + return true; + } +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/HealerAI.cs b/Projects/UOContent/Mobiles/AI/HealerAI.cs index cf5d19824..ad2b7bc3d 100644 --- a/Projects/UOContent/Mobiles/AI/HealerAI.cs +++ b/Projects/UOContent/Mobiles/AI/HealerAI.cs @@ -4,179 +4,168 @@ using Server.Spells.Fourth; using Server.Spells.Second; using Server.Targeting; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class HealerAI : BaseAI { - public class HealerAI : BaseAI + private static readonly NeedDelegate m_Cure = NeedCure; + private static readonly NeedDelegate m_GHeal = NeedGHeal; + private static readonly NeedDelegate m_LHeal = NeedLHeal; + private static readonly NeedDelegate[] m_ACure = { m_Cure }; + private static readonly NeedDelegate[] m_AGHeal = { m_GHeal }; + private static readonly NeedDelegate[] m_ALHeal = { m_LHeal }; + private static readonly NeedDelegate[] m_All = { m_Cure, m_GHeal, m_LHeal }; + + public HealerAI(BaseCreature m) : base(m) { - private static readonly NeedDelegate m_Cure = NeedCure; - private static readonly NeedDelegate m_GHeal = NeedGHeal; - private static readonly NeedDelegate m_LHeal = NeedLHeal; - private static readonly NeedDelegate[] m_ACure = { m_Cure }; - private static readonly NeedDelegate[] m_AGHeal = { m_GHeal }; - private static readonly NeedDelegate[] m_ALHeal = { m_LHeal }; - private static readonly NeedDelegate[] m_All = { m_Cure, m_GHeal, m_LHeal }; + } - public HealerAI(BaseCreature m) : base(m) + public override bool Think() + { + if (m_Mobile.Deleted) { + return false; } - public override bool Think() + var targ = m_Mobile.Target; + + if (targ != null) { - if (m_Mobile.Deleted) + var spellTarg = targ as ISpellTarget; + + if (spellTarg?.Spell is CureSpell) { - return false; + ProcessTarget(targ, m_ACure); } - - var targ = m_Mobile.Target; - - if (targ != null) + else if (spellTarg?.Spell is GreaterHealSpell) { - var spellTarg = targ as ISpellTarget; - - if (spellTarg?.Spell is CureSpell) - { - ProcessTarget(targ, m_ACure); - } - else if (spellTarg?.Spell is GreaterHealSpell) - { - ProcessTarget(targ, m_AGHeal); - } - else if (spellTarg?.Spell is HealSpell) - { - ProcessTarget(targ, m_ALHeal); - } - else - { - targ.Cancel(m_Mobile, TargetCancelType.Canceled); - } + ProcessTarget(targ, m_AGHeal); } - else + else if (spellTarg?.Spell is HealSpell) { - var toHelp = Find(m_All); - - if (toHelp != null) - { - if (NeedCure(toHelp)) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("{0} needs a cure", toHelp.Name); - } - - if (!new CureSpell(m_Mobile).Cast()) - { - new CureSpell(m_Mobile).Cast(); - } - } - else if (NeedGHeal(toHelp)) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("{0} needs a greater heal", toHelp.Name); - } - - if (!new GreaterHealSpell(m_Mobile).Cast()) - { - new HealSpell(m_Mobile).Cast(); - } - } - else if (NeedLHeal(toHelp)) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("{0} needs a lesser heal", toHelp.Name); - } - - new HealSpell(m_Mobile).Cast(); - } - } - else - { - if (AcquireFocusMob(m_Mobile.RangePerception, FightMode.Weakest, false, true, false)) - { - WalkMobileRange(m_Mobile.FocusMob, 1, false, 4, 7); - } - else - { - WalkRandomInHome(3, 2, 1); - } - } - } - - return true; - } - - private void ProcessTarget(Target targ, NeedDelegate[] func) - { - var toHelp = Find(func); - - if (toHelp != null) - { - if (targ.Range != -1 && !m_Mobile.InRange(toHelp, targ.Range)) - { - DoMove(m_Mobile.GetDirectionTo(toHelp) | Direction.Running); - } - else - { - targ.Invoke(m_Mobile, toHelp); - } + ProcessTarget(targ, m_ALHeal); } else { targ.Cancel(m_Mobile, TargetCancelType.Canceled); } } - - private Mobile Find(params NeedDelegate[] funcs) + else { - if (m_Mobile.Deleted) + var toHelp = Find(m_All); + + if (toHelp != null) { - return null; - } - - var map = m_Mobile.Map; - - if (map != null) - { - var prio = 0.0; - Mobile found = null; - - foreach (var m in m_Mobile.GetMobilesInRange(m_Mobile.RangePerception)) + if (NeedCure(toHelp)) { - if (!m_Mobile.CanSee(m) || !(m is BaseCreature) || ((BaseCreature)m).Team != m_Mobile.Team) + if (m_Mobile.Debug) { - continue; + m_Mobile.DebugSay($"{toHelp.Name} needs a cure"); } - for (var i = 0; i < funcs.Length; ++i) + if (!new CureSpell(m_Mobile).Cast()) { - if (funcs[i](m)) - { - var val = -m_Mobile.GetDistanceToSqrt(m); - - if (found == null || val > prio) - { - prio = val; - found = m; - } - - break; - } + new CureSpell(m_Mobile).Cast(); } } + else if (NeedGHeal(toHelp)) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("{toHelp.Name} needs a greater heal"); + } - return found; + if (!new GreaterHealSpell(m_Mobile).Cast()) + { + new HealSpell(m_Mobile).Cast(); + } + } + else if (NeedLHeal(toHelp)) + { + m_Mobile.DebugSay("{toHelp.Name} needs a lesser heal"); + new HealSpell(m_Mobile).Cast(); + } } + else if (AcquireFocusMob(m_Mobile.RangePerception, FightMode.Weakest, false, true, false)) + { + WalkMobileRange(m_Mobile.FocusMob, 1, false, 4, 7); + } + else + { + WalkRandomInHome(3, 2, 1); + } + } + return true; + } + + private void ProcessTarget(Target targ, NeedDelegate[] func) + { + var toHelp = Find(func); + + if (toHelp == null) + { + targ.Cancel(m_Mobile, TargetCancelType.Canceled); + } + else if (targ.Range != -1 && !m_Mobile.InRange(toHelp, targ.Range)) + { + DoMove(m_Mobile.GetDirectionTo(toHelp) | Direction.Running); + } + else + { + targ.Invoke(m_Mobile, toHelp); + } + } + + private Mobile Find(params NeedDelegate[] funcs) + { + if (m_Mobile.Deleted) + { return null; } - private static bool NeedCure(Mobile m) => m.Poisoned; + var map = m_Mobile.Map; - private static bool NeedGHeal(Mobile m) => m.Hits < m.HitsMax - 40; + if (map == null) + { + return null; + } - private static bool NeedLHeal(Mobile m) => m.Hits < m.HitsMax - 10; + var prio = 0.0; + Mobile found = null; - private delegate bool NeedDelegate(Mobile m); + foreach (var m in m_Mobile.GetMobilesInRange(m_Mobile.RangePerception)) + { + if (!m_Mobile.CanSee(m) || !(m is BaseCreature) || ((BaseCreature)m).Team != m_Mobile.Team) + { + continue; + } + + for (var i = 0; i < funcs.Length; ++i) + { + if (funcs[i](m)) + { + var val = -m_Mobile.GetDistanceToSqrt(m); + + if (found == null || val > prio) + { + prio = val; + found = m; + } + + break; + } + } + } + + return found; } -} + + private static bool NeedCure(Mobile m) => m.Poisoned; + + private static bool NeedGHeal(Mobile m) => m.Hits < m.HitsMax - 40; + + private static bool NeedLHeal(Mobile m) => m.Hits < m.HitsMax - 10; + + private delegate bool NeedDelegate(Mobile m); +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/MageAI.cs b/Projects/UOContent/Mobiles/AI/MageAI.cs index a27240072..5984c0a2f 100644 --- a/Projects/UOContent/Mobiles/AI/MageAI.cs +++ b/Projects/UOContent/Mobiles/AI/MageAI.cs @@ -10,503 +10,386 @@ using Server.Spells.Sixth; using Server.Spells.Third; using Server.Targeting; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class MageAI : BaseAI { - 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 static readonly int[] m_Offsets = { - 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 + -1, -1, + -1, 0, + -1, 1, + 0, -1, + 0, 1, + 1, -1, + 1, 0, + 1, 1, - 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 - }; - - protected int m_Combo = -1; - - private Mobile m_LastTarget; - private Point3D m_LastTargetLoc; - private long m_NextCastTime; - private long m_NextHealTime; - - private LandTarget m_RevealTarget; - - public MageAI(BaseCreature m) - : base(m) + -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 m_Combo = -1; + + private Mobile m_LastTarget; + private Point3D m_LastTargetLoc; + private long m_NextCastTime; + private long m_NextHealTime; + + private LandTarget m_RevealTarget; + + public MageAI(BaseCreature m) + : base(m) + { + } + + public virtual bool SmartAI => m_Mobile is BaseVendor or BaseEscortable or Changeling; + + public virtual bool IsNecromancer => Core.AOS && m_Mobile.Skills.Necromancy.Value > 50; + + public override bool Think() + { + if (m_Mobile.Deleted) { + return false; } - public virtual bool SmartAI => m_Mobile is BaseVendor or BaseEscortable or Changeling; + return ProcessTarget() || base.Think(); + } - public virtual bool IsNecromancer => Core.AOS && m_Mobile.Skills.Necromancy.Value > 50; + public virtual double ScaleBySkill(double v, SkillName skill) => v * m_Mobile.Skills[skill].Value / 100; - public override bool Think() + public override bool DoActionWander() + { + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { - if (m_Mobile.Deleted) + if (m_Mobile.Debug) { - return false; + m_Mobile.DebugSay($"I am going to attack {m_Mobile.FocusMob.Name}"); } - return ProcessTarget() || base.Think(); + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + m_NextCastTime = Core.TickCount; } - - public virtual double ScaleBySkill(double v, SkillName skill) => v * m_Mobile.Skills[skill].Value / 100; - - public override bool DoActionWander() + else if (SmartAI && m_Mobile.Mana < m_Mobile.ManaMax && !m_Mobile.Meditating) { - 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 = Core.TickCount; - } - else if (SmartAI && m_Mobile.Mana < m_Mobile.ManaMax && !m_Mobile.Meditating) + if (m_Mobile.Debug) { m_Mobile.DebugSay("I am going to meditate"); - - m_Mobile.UseSkill(SkillName.Meditation); } - else + + m_Mobile.UseSkill(SkillName.Meditation); + } + else + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I am wandering"); - - m_Mobile.Warmode = false; - - base.DoActionWander(); - - if (Utility.RandomDouble() < 0.05) - { - var spell = CheckCastHealingSpell(); - - spell?.Cast(); - } } - return true; + m_Mobile.Warmode = false; + + base.DoActionWander(); + + if (Utility.RandomDouble() < 0.05) + { + var spell = CheckCastHealingSpell(); + + spell?.Cast(); + } } - private Spell CheckCastHealingSpell() - { - // If I'm poisoned, always attempt to cure. - if (m_Mobile.Poisoned) - { - return new CureSpell(m_Mobile); - } + return true; + } - // Summoned creatures never heal themselves. - if (m_Mobile.Summoned) + private Spell CheckCastHealingSpell() + { + // If I'm poisoned, always attempt to cure. + if (m_Mobile.Poisoned) + { + return new CureSpell(m_Mobile); + } + + // Summoned creatures never heal themselves. + if (m_Mobile.Summoned) + { + return null; + } + + if (m_Mobile.Controlled) + { + if (Core.TickCount - m_NextHealTime < 0) { return null; } + } - if (m_Mobile.Controlled) + if (!SmartAI) + { + if (ScaleBySkill(HealChance, SkillName.Magery) < Utility.RandomDouble()) { - if (Core.TickCount - m_NextHealTime < 0) - { - return null; - } + return null; } + } + else if (Utility.Random(0, 4 + (m_Mobile.Hits == 0 ? m_Mobile.HitsMax : m_Mobile.HitsMax / m_Mobile.Hits)) < 3) + { + return null; + } - if (!SmartAI) + Spell spell = null; + + if (m_Mobile.Hits < m_Mobile.HitsMax - 50) + { + if (UseNecromancy()) { - if (ScaleBySkill(HealChance, SkillName.Magery) < Utility.RandomDouble()) - { - return null; - } + m_Mobile.UseSkill(SkillName.SpiritSpeak); } 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) - { - if (UseNecromancy()) - { - m_Mobile.UseSkill(SkillName.SpiritSpeak); - } - else - { - spell = new GreaterHealSpell(m_Mobile); - } - } - else if (m_Mobile.Hits < m_Mobile.HitsMax - 10) - { - spell = new HealSpell(m_Mobile); - } - - double delay; - - if (m_Mobile.Int >= 500) - { - delay = Utility.RandomMinMax(7, 10); - } - else - { - delay = Math.Sqrt(600 - m_Mobile.Int); - } - - m_NextHealTime = Core.TickCount + (int)TimeSpan.FromSeconds(delay).TotalMilliseconds; - - 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); - } + spell = new GreaterHealSpell(m_Mobile); } } - - public void RunFrom(Mobile m) + else if (m_Mobile.Hits < m_Mobile.HitsMax - 10) { - Run((m_Mobile.GetDirectionTo(m) - 4) & Direction.Mask); + spell = new HealSpell(m_Mobile); } - public void OnFailedMove() + var delay = m_Mobile.Int >= 500 ? Utility.RandomMinMax(7, 10) : Math.Sqrt(600 - m_Mobile.Int); + + m_NextHealTime = Core.TickCount + (int)TimeSpan.FromSeconds(delay).TotalMilliseconds; + + return spell; + } + + public void RunTo(Mobile m) + { + if (!SmartAI) { - if (!m_Mobile.DisallowAllMoves && (SmartAI - ? Utility.Random(4) == 0 - : ScaleBySkill(TeleportChance, SkillName.Magery) > Utility.RandomDouble())) + if (!MoveTo(m, true, m_Mobile.RangeFight)) { - m_Mobile.Target?.Cancel(m_Mobile, TargetCancelType.Canceled); - - new TeleportSpell(m_Mobile).Cast(); - - m_Mobile.DebugSay("I am stuck, I'm going to try teleporting away"); + OnFailedMove(); } - 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"); - } + return; } - public void Run(Direction d) + if (m.Paralyzed || m.Frozen) { - if (m_Mobile.Spell?.IsCasting == true || m_Mobile.Paralyzed || m_Mobile.Frozen || - m_Mobile.DisallowAllMoves) + if (m_Mobile.InRange(m, 1)) { - return; + RunFrom(m); } - - m_Mobile.Direction = d | Direction.Running; - - if (!DoMove(m_Mobile.Direction, true)) + else if (!m_Mobile.InRange(m, m_Mobile.RangeFight > 2 ? m_Mobile.RangeFight : 2) && !MoveTo(m, true, 1)) { OnFailedMove(); } } - - public virtual bool UseNecromancy() + else if (!m_Mobile.InRange(m, m_Mobile.RangeFight)) { - if (IsNecromancer) + if (!MoveTo(m, true, 1)) { - return Utility.Random( - m_Mobile.Skills.Magery.BaseFixedPoint + - m_Mobile.Skills.Necromancy.BaseFixedPoint - ) >= - m_Mobile.Skills.Magery.BaseFixedPoint; + 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 + : ScaleBySkill(TeleportChance, SkillName.Magery) > Utility.RandomDouble())) + { + m_Mobile.Target?.Cancel(m_Mobile, TargetCancelType.Canceled); + + new TeleportSpell(m_Mobile).Cast(); + + if (m_Mobile.Debug) + { + 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 {m_Mobile.FocusMob.Name}"); } - return false; + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I am stuck"); + } + } + + public void Run(Direction d) + { + if (m_Mobile.Spell?.IsCasting == true || m_Mobile.Paralyzed || m_Mobile.Frozen || + m_Mobile.DisallowAllMoves) + { + return; } - public virtual Spell GetRandomDamageSpell() => - UseNecromancy() ? GetRandomDamageSpellNecro() : GetRandomDamageSpellMage(); + m_Mobile.Direction = d | Direction.Running; - public virtual Spell GetRandomDamageSpellNecro() + if (!DoMove(m_Mobile.Direction, true)) { - var bound = m_Mobile.Skills.Necromancy.Value >= 100 ? 5 : 3; + OnFailedMove(); + } + } - switch (Utility.Random(bound)) + public virtual bool UseNecromancy() + { + var magery = m_Mobile.Skills.Magery.BaseFixedPoint; + var necro = m_Mobile.Skills.Necromancy.BaseFixedPoint; + return IsNecromancer && Utility.Random(magery + necro) >= magery; + } + + public virtual Spell GetRandomDamageSpell() => + UseNecromancy() ? GetRandomDamageSpellNecro() : GetRandomDamageSpellMage(); + + public virtual Spell GetRandomDamageSpellNecro() + { + var bound = m_Mobile.Skills.Necromancy.Value >= 100 ? 5 : 3; + + return Utility.Random(bound) switch + { + 0 => new PainSpikeSpell(m_Mobile), + 1 => new PoisonStrikeSpell(m_Mobile), + 2 => new StrangleSpell(m_Mobile), + 3 => new WitherSpell(m_Mobile), + _ => new VengefulSpiritSpell(m_Mobile) + }; + } + + public virtual Spell GetRandomDamageSpellMage() + { + var maxCircle = Math.Clamp((int)((m_Mobile.Skills.Magery.Value + 20.0) / (100.0 / 7.0)), 1, 8); + + return Utility.Random(maxCircle * 2) switch + { + 0 => new MagicArrowSpell(m_Mobile), + 1 => new MagicArrowSpell(m_Mobile), + 2 => new HarmSpell(m_Mobile), + 3 => new HarmSpell(m_Mobile), + 4 => new FireballSpell(m_Mobile), + 5 => new FireballSpell(m_Mobile), + 6 => new LightningSpell(m_Mobile), + 7 => new LightningSpell(m_Mobile), + 8 => new MindBlastSpell(m_Mobile), + 9 => new MindBlastSpell(m_Mobile), + 10 => new EnergyBoltSpell(m_Mobile), + 11 => new ExplosionSpell(m_Mobile), + _ => new FlameStrikeSpell(m_Mobile) + }; + } + + public virtual Spell GetRandomCurseSpell() => + UseNecromancy() ? GetRandomCurseSpellNecro() : GetRandomCurseSpellMage(); + + public virtual Spell GetRandomCurseSpellNecro() + { + return Utility.Random(4) switch + { + 0 => new BloodOathSpell(m_Mobile), + 1 => new CorpseSkinSpell(m_Mobile), + 2 => new EvilOmenSpell(m_Mobile), + _ => new MindRotSpell(m_Mobile) + }; + } + + public virtual Spell GetRandomCurseSpellMage() + { + if (m_Mobile.Skills.Magery.Value >= 40.0 && Utility.Random(4) == 0) + { + return new CurseSpell(m_Mobile); + } + + return Utility.Random(3) switch + { + 0 => new WeakenSpell(m_Mobile), + 1 => new ClumsySpell(m_Mobile), + _ => new FeeblemindSpell(m_Mobile) + }; + } + + public virtual Spell GetRandomManaDrainSpell() + { + if (m_Mobile.Skills.Magery.Value >= 80.0 && Utility.RandomBool()) + { + return new ManaVampireSpell(m_Mobile); + } + + return new ManaDrainSpell(m_Mobile); + } + + public virtual Spell DoDispel(Mobile toDispel) + { + if (!SmartAI) + { + return ScaleBySkill(DispelChance, SkillName.Magery) > Utility.RandomDouble() + ? new DispelSpell(m_Mobile) + : ChooseSpell(toDispel); + } + + var spell = CheckCastHealingSpell(); + + if (spell == null) + { + var distance = (int)m_Mobile.GetDistanceToSqrt(toDispel); + if (!m_Mobile.DisallowAllMoves && distance > 0 && Utility.Random(distance) == 0) { - case 0: - m_Mobile.DebugSay("Pain Spike"); - return new PainSpikeSpell(m_Mobile); - case 1: - m_Mobile.DebugSay("Poison Strike"); - return new PoisonStrikeSpell(m_Mobile); - case 2: - m_Mobile.DebugSay("Strangle"); - return new StrangleSpell(m_Mobile); - case 3: - m_Mobile.DebugSay("Wither"); - return new WitherSpell(m_Mobile); - default: - m_Mobile.DebugSay("Vengeful Spirit"); - return new VengefulSpiritSpell(m_Mobile); + spell = new TeleportSpell(m_Mobile); + } + else if (Utility.Random(3) == 0 && !m_Mobile.InRange(toDispel, 3) && !toDispel.Paralyzed && !toDispel.Frozen) + { + spell = new ParalyzeSpell(m_Mobile); + } + else + { + spell = new DispelSpell(m_Mobile); } } - public virtual Spell GetRandomDamageSpellMage() + return spell; + } + + public virtual Spell ChooseSpell(Mobile c) + { + Spell spell; + + if (!SmartAI) { - var maxCircle = Math.Clamp((int)((m_Mobile.Skills.Magery.Value + 20.0) / (100.0 / 7.0)), 1, 8); - - return Utility.Random(maxCircle * 2) switch - { - 0 => new MagicArrowSpell(m_Mobile), - 1 => new MagicArrowSpell(m_Mobile), - 2 => new HarmSpell(m_Mobile), - 3 => new HarmSpell(m_Mobile), - 4 => new FireballSpell(m_Mobile), - 5 => new FireballSpell(m_Mobile), - 6 => new LightningSpell(m_Mobile), - 7 => new LightningSpell(m_Mobile), - 8 => new MindBlastSpell(m_Mobile), - 9 => new MindBlastSpell(m_Mobile), - 10 => new EnergyBoltSpell(m_Mobile), - 11 => new ExplosionSpell(m_Mobile), - _ => new FlameStrikeSpell(m_Mobile) - }; - } - - public virtual Spell GetRandomCurseSpell() => - UseNecromancy() ? GetRandomCurseSpellNecro() : GetRandomCurseSpellMage(); - - public virtual Spell GetRandomCurseSpellNecro() - { - switch (Utility.Random(4)) - { - case 0: - m_Mobile.DebugSay("Blood Oath"); - return new BloodOathSpell(m_Mobile); - case 1: - m_Mobile.DebugSay("Corpse Skin"); - return new CorpseSkinSpell(m_Mobile); - case 2: - m_Mobile.DebugSay("Evil Omen"); - return new EvilOmenSpell(m_Mobile); - default: - m_Mobile.DebugSay("Mind Rot"); - return new MindRotSpell(m_Mobile); - } - } - - public virtual Spell GetRandomCurseSpellMage() - { - if (m_Mobile.Skills.Magery.Value >= 40.0 && Utility.Random(4) == 0) - { - return new CurseSpell(m_Mobile); - } - - return Utility.Random(3) switch - { - 0 => new WeakenSpell(m_Mobile), - 1 => new ClumsySpell(m_Mobile), - _ => new FeeblemindSpell(m_Mobile) - }; - } - - public virtual Spell GetRandomManaDrainSpell() - { - if (m_Mobile.Skills.Magery.Value >= 80.0 && Utility.RandomBool()) - { - return new ManaVampireSpell(m_Mobile); - } - - return new ManaDrainSpell(m_Mobile); - } - - public virtual Spell DoDispel(Mobile toDispel) - { - if (!SmartAI) - { - return ScaleBySkill(DispelChance, SkillName.Magery) > Utility.RandomDouble() - ? new DispelSpell(m_Mobile) - : ChooseSpell(toDispel); - } - - var spell = CheckCastHealingSpell(); - - if (spell == null) - { - var distance = (int)m_Mobile.GetDistanceToSqrt(toDispel); - if (!m_Mobile.DisallowAllMoves && distance > 0 && Utility.Random(distance) == 0) - { - spell = new TeleportSpell(m_Mobile); - } - else if (Utility.Random(3) == 0 && !m_Mobile.InRange(toDispel, 3) && !toDispel.Paralyzed && !toDispel.Frozen) - { - spell = new ParalyzeSpell(m_Mobile); - } - else - { - spell = new DispelSpell(m_Mobile); - } - } - - return spell; - } - - public virtual Spell ChooseSpell(Mobile c) - { - Spell spell; - - if (!SmartAI) - { - spell = CheckCastHealingSpell(); - - if (spell != null) - { - return spell; - } - - if (IsNecromancer) - { - var psDamage = - (m_Mobile.Skills.SpiritSpeak.Value - c.Skills.MagicResist.Value) / 10 + - (c.Player ? 18 : 30); - - if (psDamage > c.Hits) - { - return new PainSpikeSpell(m_Mobile); - } - } - - switch (Utility.Random(16)) - { - case 0: - case 1: // Poison them - { - if (c.Poisoned) - { - goto default; - } - - m_Mobile.DebugSay("Attempting to poison"); - - spell = new PoisonSpell(m_Mobile); - break; - } - case 2: // Bless ourselves - { - m_Mobile.DebugSay("Blessing myself"); - - spell = new BlessSpell(m_Mobile); - break; - } - case 3: - case 4: // Curse them - { - m_Mobile.DebugSay("Attempting to curse"); - - spell = GetRandomCurseSpell(); - break; - } - case 5: // Paralyze them - { - if (c.Paralyzed || m_Mobile.Skills.Magery.Value <= 50.0) - { - goto default; - } - - m_Mobile.DebugSay("Attempting to paralyze"); - - spell = new ParalyzeSpell(m_Mobile); - break; - } - case 6: // Drain mana - { - m_Mobile.DebugSay("Attempting to drain mana"); - - spell = GetRandomManaDrainSpell(); - break; - } - case 7: // Invis ourselves - { - if (Utility.RandomBool()) - { - goto default; - } - - m_Mobile.DebugSay("Attempting to invis myself"); - - spell = new InvisibilitySpell(m_Mobile); - break; - } - default: // Damage them - { - m_Mobile.DebugSay("Just doing damage"); - - spell = GetRandomDamageSpell(); - break; - } - } - - return spell; - } - spell = CheckCastHealingSpell(); if (spell != null) @@ -514,581 +397,693 @@ namespace Server.Mobiles return spell; } - switch (Utility.Random(3)) + if (IsNecromancer) { - case 0: // Poison them + var psDamage = + (m_Mobile.Skills.SpiritSpeak.Value - c.Skills.MagicResist.Value) / 10 + + (c.Player ? 18 : 30); + + if (psDamage > c.Hits) + { + return new PainSpikeSpell(m_Mobile); + } + } + + switch (Utility.Random(16)) + { + case 0: + case 1: // Poison them { if (c.Poisoned) { - goto case 1; + goto default; } + m_Mobile.DebugSay("Attempting to poison"); + spell = new PoisonSpell(m_Mobile); break; } - case 1: // Deal some damage + case 2: // Bless ourselves { + m_Mobile.DebugSay("Blessing myself"); + + spell = new BlessSpell(m_Mobile); + break; + } + case 3: + case 4: // Curse them + { + m_Mobile.DebugSay("Attempting to curse"); + + spell = GetRandomCurseSpell(); + break; + } + case 5: // Paralyze them + { + if (c.Paralyzed || m_Mobile.Skills.Magery.Value <= 50.0) + { + goto default; + } + + m_Mobile.DebugSay("Attempting to paralyze"); + + spell = new ParalyzeSpell(m_Mobile); + break; + } + case 6: // Drain mana + { + m_Mobile.DebugSay("Attempting to drain mana"); + + spell = GetRandomManaDrainSpell(); + break; + } + case 7: // Invis ourselves + { + if (Utility.RandomBool()) + { + goto default; + } + + m_Mobile.DebugSay("Attempting to invis myself"); + + spell = new InvisibilitySpell(m_Mobile); + break; + } + default: // Damage them + { + m_Mobile.DebugSay("Just doing damage"); + spell = GetRandomDamageSpell(); - break; } - default: // Set up a combo + } + + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"Casting {spell.Name}"); + } + + return spell; + } + + spell = CheckCastHealingSpell(); + + if (spell != null) + { + return spell; + } + + switch (Utility.Random(3)) + { + case 0: // Poison them + { + if (c.Poisoned) { - 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"); + goto case 1; + } - m_Mobile.UseSkill(SkillName.Meditation); - } - else if (!c.Poisoned) - { - spell = new ParalyzeSpell(m_Mobile); - } - } - else if (m_Mobile.Mana > 60) + spell = new PoisonSpell(m_Mobile); + break; + } + case 1: // Deal some damage + { + spell = GetRandomDamageSpell(); + break; + } + default: // Set up a combo + { + if (m_Mobile.Mana is > 15 and < 40) + { + if (c.Paralyzed && !c.Poisoned && !m_Mobile.Meditating) { - if (Utility.RandomBool() && !c.Paralyzed && !c.Frozen && !c.Poisoned) - { - m_Combo = 0; - spell = new ParalyzeSpell(m_Mobile); - } - else - { - m_Combo = 1; - spell = new ExplosionSpell(m_Mobile); - } + m_Mobile.DebugSay("I am going to meditate"); + + m_Mobile.UseSkill(SkillName.Meditation); } + else if (!c.Poisoned) + { + spell = new ParalyzeSpell(m_Mobile); + } + } + else if (m_Mobile.Mana > 60) + { + if (Utility.RandomBool() && !c.Paralyzed && !c.Frozen && !c.Poisoned) + { + m_Combo = 0; + spell = new ParalyzeSpell(m_Mobile); + } + else + { + m_Combo = 1; + spell = new ExplosionSpell(m_Mobile); + } + } + + break; + } + } + + if (m_Mobile.Debug) + { + if (spell != null) + { + m_Mobile.DebugSay($"Casting {spell.Name}"); + } + else + { + m_Mobile.DebugSay("I don't have a spell to use!"); + } + } + return spell; + } + + public virtual Spell DoCombo(Mobile c) + { + Spell spell = null; + + if (m_Combo == 0) + { + spell = new ExplosionSpell(m_Mobile); + ++m_Combo; // Move to next spell + } + else if (m_Combo == 1) + { + spell = new WeakenSpell(m_Mobile); + ++m_Combo; // Move to next spell + } + else if (m_Combo == 2) + { + if (!c.Poisoned) + { + spell = new PoisonSpell(m_Mobile); + } + else if (IsNecromancer) + { + spell = new StrangleSpell(m_Mobile); + } + + ++m_Combo; // Move to next spell + } + + if (m_Combo == 3 && spell == null) + { + switch (Utility.Random(IsNecromancer ? 4 : 3)) + { + case 0: + { + if (c.Int < c.Dex) + { + spell = new FeeblemindSpell(m_Mobile); + } + else + { + spell = new ClumsySpell(m_Mobile); + } + + ++m_Combo; // Move to next spell + break; + } + case 1: + { + spell = new EnergyBoltSpell(m_Mobile); + m_Combo = -1; // Reset combo state + break; + } + case 2: + { + spell = new FlameStrikeSpell(m_Mobile); + m_Combo = -1; // Reset combo state + break; + } + default: + { + spell = new PainSpikeSpell(m_Mobile); + m_Combo = -1; // Reset combo state break; } } - - return spell; + } + else if (m_Combo == 4 && spell == null) + { + spell = new MindBlastSpell(m_Mobile); + m_Combo = -1; } - public virtual Spell DoCombo(Mobile c) + return spell; + } + + private TimeSpan GetDelay(Spell spell) + { + if (SmartAI || spell is DispelSpell) { - Spell spell = null; - - if (m_Combo == 0) - { - spell = new ExplosionSpell(m_Mobile); - ++m_Combo; // Move to next spell - } - else if (m_Combo == 1) - { - spell = new WeakenSpell(m_Mobile); - ++m_Combo; // Move to next spell - } - else if (m_Combo == 2) - { - if (!c.Poisoned) - { - spell = new PoisonSpell(m_Mobile); - } - else if (IsNecromancer) - { - spell = new StrangleSpell(m_Mobile); - } - - ++m_Combo; // Move to next spell - } - - if (m_Combo == 3 && spell == null) - { - switch (Utility.Random(IsNecromancer ? 4 : 3)) - { - case 0: - { - if (c.Int < c.Dex) - { - spell = new FeeblemindSpell(m_Mobile); - } - else - { - spell = new ClumsySpell(m_Mobile); - } - - ++m_Combo; // Move to next spell - - break; - } - case 1: - { - spell = new EnergyBoltSpell(m_Mobile); - m_Combo = -1; // Reset combo state - break; - } - case 2: - { - spell = new FlameStrikeSpell(m_Mobile); - m_Combo = -1; // Reset combo state - break; - } - default: - { - spell = new PainSpikeSpell(m_Mobile); - m_Combo = -1; // Reset combo state - break; - } - } - } - else if (m_Combo == 4 && spell == null) - { - spell = new MindBlastSpell(m_Mobile); - m_Combo = -1; - } - - return spell; + return TimeSpan.FromSeconds(m_Mobile.ActiveSpeed); } - private TimeSpan GetDelay(Spell spell) + var del = ScaleBySkill(3.0, SkillName.Magery); + var min = 6.0 - del * 0.75; + var max = 6.0 - del * 1.25; + + return TimeSpan.FromSeconds(min + (max - min) * Utility.RandomDouble()); + } + + public override bool DoActionCombat() + { + var c = m_Mobile.Combatant; + m_Mobile.Warmode = true; + + if (c?.Deleted != false || !c.Alive || c.IsDeadBondedPet || !m_Mobile.CanSee(c) || + !m_Mobile.CanBeHarmful(c, false) || c.Map != m_Mobile.Map) { - if (SmartAI || spell is DispelSpell) + // 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)) { - return TimeSpan.FromSeconds(m_Mobile.ActiveSpeed); - } - - var del = ScaleBySkill(3.0, SkillName.Magery); - var min = 6.0 - del * 0.75; - var max = 6.0 - del * 1.25; - - return TimeSpan.FromSeconds(min + (max - min) * Utility.RandomDouble()); - } - - public override bool DoActionCombat() - { - var c = m_Mobile.Combatant; - m_Mobile.Warmode = true; - - if (c?.Deleted != false || !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 + $"Something happened to my combatant, so I am going to fight {m_Mobile.FocusMob.Name}" ); - - m_Mobile.Combatant = c = m_Mobile.FocusMob; - m_Mobile.FocusMob = null; } - else + + m_Mobile.Combatant = c = m_Mobile.FocusMob; + m_Mobile.FocusMob = null; + } + else + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("Something happened to my combatant, and nothing is around. I am on guard."); - Action = ActionType.Guard; - return true; } + + 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.Wrestling.Value >= 80.0 && - m_Mobile.Skills.Anatomy.Value >= 80.0) - { - EventSink.InvokeStunRequest(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? - bool flee; - - if (m_Mobile.Hits < c.Hits) - { - // We are more hurt than them - var 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) - { - m_Mobile.DebugSay("I am going to flee from {0}", c.Name); - - Action = ActionType.Flee; - return true; - } - } - } - - if (m_Mobile.Spell == null && Core.TickCount - m_NextCastTime >= 0 && m_Mobile.InRange(c, Core.ML ? 10 : 12)) - { - // We are ready to cast a spell - Spell spell; - 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); - } - else if (toDispel != null) // Something dispellable is attacking us - { - m_Mobile.DebugSay("I am going to dispel {0}", toDispel); - - spell = DoDispel(toDispel); - } - else - { - spell = SmartAI switch - { - // We are doing a spell combo - true when m_Combo != -1 => DoCombo(c), - // They have a heal spell out - true when - !c.Poisoned && - c.Spell is HealSpell or GreaterHealSpell => new PoisonSpell(m_Mobile), - _ => 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, Core.ML ? 10 : 12)) - { - RunTo(toDispel); - } - } - else - { - RunTo(c); - } - - spell?.Cast(); - - m_NextCastTime = Core.TickCount + (int)GetDelay(spell).TotalMilliseconds; - } - else if (m_Mobile.Spell?.IsCasting != true) - { - RunTo(c); - } - - m_LastTarget = c; - m_LastTargetLoc = c.Location; - - return true; } - public override bool DoActionGuard() + if (!m_Mobile.InLOS(c)) { - if (m_LastTarget?.Hidden == true) + if (m_Mobile.Debug) { - var map = m_Mobile.Map; - - if (map == null || !m_Mobile.InRange(m_LastTargetLoc, Core.ML ? 10 : 12)) - { - m_LastTarget = null; - } - else if (m_Mobile.Spell == null && Core.TickCount - m_NextCastTime >= 0) - { - m_Mobile.DebugSay("I am going to reveal my last target"); - - m_RevealTarget = new LandTarget(m_LastTargetLoc, map); - Spell spell = new RevealSpell(m_Mobile); - - if (spell.Cast()) - { - m_LastTarget = null; // only do it once - } - - m_NextCastTime = Core.TickCount + (int)GetDelay(spell).TotalMilliseconds; - } + m_Mobile.DebugSay("I can't see my 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 - { - if (!m_Mobile.Controlled) + if (m_Mobile.Debug) { - ProcessTarget(); - - var spell = CheckCastHealingSpell(); - - spell?.Cast(); + m_Mobile.DebugSay($"I will switch to {m_Mobile.FocusMob.Name}"); } - 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)) - { - m_Mobile.DebugSay("I am scared of {0}", m_Mobile.FocusMob.Name); - - RunFrom(m_Mobile.FocusMob); + m_Mobile.Combatant = c = m_Mobile.FocusMob; m_Mobile.FocusMob = null; + } + } - if (m_Mobile.Poisoned && Utility.Random(0, 5) == 0) + if (!Core.AOS && SmartAI && !m_Mobile.StunReady && m_Mobile.Skills.Wrestling.Value >= 80.0 && + m_Mobile.Skills.Anatomy.Value >= 80.0) + { + EventSink.InvokeStunRequest(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) + { + if (m_Mobile.Debug) { - new CureSpell(m_Mobile).Cast(); + 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 && m_Mobile.Hits < m_Mobile.HitsMax * 20 / 100) + { + // We are low on health, should we flee? + // (10 + diff)% chance to flee + var fleeChance = 10 + Math.Max(0, c.Hits - m_Mobile.Hits); + + if (Utility.Random(0, 100) > fleeChance) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I am going to flee from {c.Name}"); + } + + Action = ActionType.Flee; + return true; + } + } + + if (m_Mobile.Spell == null && Core.TickCount - m_NextCastTime >= 0 && m_Mobile.InRange(c, Core.ML ? 10 : 12)) + { + // We are ready to cast a spell + Spell spell; + var toDispel = FindDispelTarget(true); + + if (m_Mobile.Poisoned) // Top cast priority is cure + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I am going to cure myself"); + } + + spell = new CureSpell(m_Mobile); + } + else if (toDispel != null) // Something dispellable is attacking us + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I am going to dispel {toDispel}"); + } + + spell = DoDispel(toDispel); } else { - m_Mobile.DebugSay("Area seems clear, but my guard is up"); - - Action = ActionType.Guard; - m_Mobile.Warmode = true; + spell = SmartAI switch + { + // We are doing a spell combo + true when m_Combo != -1 => DoCombo(c), + // They have a heal spell out + true when + !c.Poisoned && + c.Spell is HealSpell or GreaterHealSpell => new PoisonSpell(m_Mobile), + _ => ChooseSpell(c) + }; } - return true; + // Now we have a spell picked + // Move first before casting + + if (!SmartAI || toDispel == null) + { + RunTo(c); + } + else if (m_Mobile.InRange(toDispel, 10)) + { + RunFrom(toDispel); + } + else if (!m_Mobile.InRange(toDispel, Core.ML ? 10 : 12)) + { + RunTo(toDispel); + } + + spell?.Cast(); + + m_NextCastTime = Core.TickCount + (int)GetDelay(spell).TotalMilliseconds; + } + else if (m_Mobile.Spell?.IsCasting != true) + { + RunTo(c); } - public Mobile FindDispelTarget(bool activeOnly) + m_LastTarget = c; + m_LastTargetLoc = c.Location; + + return true; + } + + public override bool DoActionGuard() + { + if (m_LastTarget?.Hidden == true) { - 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; - - if (comb?.Deleted == false && 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) + if (map == null || !m_Mobile.InRange(m_LastTargetLoc, Core.ML ? 10 : 12)) { - Mobile active = null, inactive = null; - double actPrio = 0.0, inactPrio = 0.0; - - var comb = m_Mobile.Combatant; - - if (comb?.Deleted == false && comb.Alive && !comb.IsDeadBondedPet && CanDispel(comb)) + m_LastTarget = null; + } + else if (m_Mobile.Spell == null && Core.TickCount - m_NextCastTime >= 0) + { + if (m_Mobile.Debug) { - active = inactive = comb; - actPrio = inactPrio = m_Mobile.GetDistanceToSqrt(comb); + m_Mobile.DebugSay("I am going to reveal my last target"); } - foreach (var m in m_Mobile.GetMobilesInRange(Core.ML ? 10 : 12)) + m_RevealTarget = new LandTarget(m_LastTargetLoc, map); + Spell spell = new RevealSpell(m_Mobile); + + if (spell.Cast()) { - if (m != m_Mobile && CanDispel(m)) - { - var 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; - } - } + m_LastTarget = null; // only do it once } - return active ?? inactive; + m_NextCastTime = Core.TickCount + (int)GetDelay(spell).TotalMilliseconds; + } + } + + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I am going to attack {m_Mobile.FocusMob.Name}"); } + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + if (!m_Mobile.Controlled) + { + ProcessTarget(); + + var spell = CheckCastHealingSpell(); + + 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) + { + if (m_Mobile.Debug) + { + 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 {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).Cast(); + } + } + else + { + if (m_Mobile.Debug) + { + 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; } - public bool CanDispel(Mobile m) => - m is BaseCreature creature && creature.Summoned && m_Mobile.CanBeHarmful(creature, false) && - !creature.IsAnimatedDead; - - private bool ProcessTarget() + if (activeOnly) { - var targ = m_Mobile.Target; + var aggressed = m_Mobile.Aggressed; + var aggressors = m_Mobile.Aggressors; - if (targ == null) + Mobile active = null; + var activePrio = 0.0; + + var comb = m_Mobile.Combatant; + + if (comb?.Deleted == false && comb.Alive && !comb.IsDeadBondedPet && + m_Mobile.InRange(comb, Core.ML ? 10 : 12) && CanDispel(comb)) { - return false; - } + active = comb; + activePrio = m_Mobile.GetDistanceToSqrt(comb); - var spellTarg = targ as ISpellTarget; - - 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 = m_Mobile; - } - else if (isDispel) - { - toTarget = FindDispelTarget(false); - - if (!SmartAI && toTarget != null) + if (activePrio <= 2) { - RunTo(toTarget); - } - else if (toTarget != null && m_Mobile.InRange(toTarget, 10)) - { - RunFrom(toTarget); + return active; } } - else if (SmartAI && (isParalyze || isTeleport)) + + for (var i = 0; i < aggressed.Count; ++i) { - toTarget = FindDispelTarget(true); + var info = aggressed[i]; + var m = info.Defender; - if (toTarget == null) + if (m != comb && m.Combatant == m_Mobile && m_Mobile.InRange(m, Core.ML ? 10 : 12) && CanDispel(m)) { - toTarget = m_Mobile.Combatant; + var prio = m_Mobile.GetDistanceToSqrt(m); - if (toTarget != null) + if (active == null || prio < activePrio) { - RunTo(toTarget); + active = m; + activePrio = prio; + + if (activePrio <= 2) + { + return active; + } } } - else if (m_Mobile.InRange(toTarget, 10)) + } + + 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)) { - RunFrom(toTarget); - teleportAway = true; - } - else - { - teleportAway = true; + var prio = m_Mobile.GetDistanceToSqrt(m); + + if (active == null || prio < activePrio) + { + active = m; + activePrio = prio; + + if (activePrio <= 2) + { + return active; + } + } } } - else + + 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; + + if (comb?.Deleted == false && comb.Alive && !comb.IsDeadBondedPet && CanDispel(comb)) + { + active = inactive = comb; + actPrio = inactPrio = m_Mobile.GetDistanceToSqrt(comb); + } + + foreach (var m in m_Mobile.GetMobilesInRange(Core.ML ? 10 : 12)) + { + if (m != m_Mobile && CanDispel(m)) + { + var 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) => + m is BaseCreature creature && creature.Summoned && m_Mobile.CanBeHarmful(creature, false) && + !creature.IsAnimatedDead; + + private bool ProcessTarget() + { + var targ = m_Mobile.Target; + + if (targ == null) + { + return false; + } + + var spellTarg = targ as ISpellTarget; + + 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 = 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; @@ -1097,93 +1092,111 @@ namespace Server.Mobiles RunTo(toTarget); } } - - if ((targ.Flags & TargetFlags.Harmful) != 0 && toTarget != null) + else if (m_Mobile.InRange(toTarget, 10)) { - 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 (isReveal && m_RevealTarget != null) - { - targ.Invoke(m_Mobile, m_RevealTarget); + RunFrom(toTarget); + teleportAway = true; } else { - var map = m_Mobile.Map; + teleportAway = true; + } + } + else + { + toTarget = m_Mobile.Combatant; - if (map != null && isTeleport && toTarget != null) + 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 (isReveal && m_RevealTarget != null) + { + targ.Invoke(m_Mobile, m_RevealTarget); + } + else + { + var map = m_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 teleRange = targ.Range >= 0 ? targ.Range : - Core.ML ? 11 : 12; + var rx = m_Mobile.X - toTarget.X; + var ry = m_Mobile.Y - toTarget.Y; - int px, py; + var d = m_Mobile.GetDistanceToSqrt(toTarget); - if (teleportAway) + 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)) { - 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; - } - } - - 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.Invoke(m_Mobile, lt); + return true; } } - targ.Cancel(m_Mobile, TargetCancelType.Canceled); + 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; + } + } } - return true; + targ.Cancel(m_Mobile, TargetCancelType.Canceled); } + + return true; } -} +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/MeleeAI.cs b/Projects/UOContent/Mobiles/AI/MeleeAI.cs index ba60c82a7..e3ef2da4d 100644 --- a/Projects/UOContent/Mobiles/AI/MeleeAI.cs +++ b/Projects/UOContent/Mobiles/AI/MeleeAI.cs @@ -1,175 +1,176 @@ -namespace Server.Mobiles -{ - public class MeleeAI : BaseAI - { - public MeleeAI(BaseCreature m) : base(m) - { - } +using System; - public override bool DoActionWander() +namespace Server.Mobiles; + +public class MeleeAI : BaseAI +{ + public MeleeAI(BaseCreature m) : base(m) + { + } + + public override bool DoActionWander() + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have no combatant"); + } - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + { + if (m_Mobile.Debug) { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I have detected {0}, attacking", m_Mobile.FocusMob.Name); - } + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name}, attacking"); + } - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionWander(); + } + + return true; + } + + public override bool DoActionCombat() + { + var combatant = m_Mobile.Combatant; + + if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map || !combatant.Alive || + combatant.IsDeadBondedPet) + { + if (m_Mobile.Debug) { - base.DoActionWander(); + m_Mobile.DebugSay("My combatant is gone, so my guard is up"); } + Action = ActionType.Guard; + return true; } - public override bool DoActionCombat() + if (!m_Mobile.InRange(combatant, m_Mobile.RangePerception)) { - var combatant = m_Mobile.Combatant; + // They are somewhat far away, can we find something else? - if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map || !combatant.Alive || - combatant.IsDeadBondedPet) + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { - m_Mobile.DebugSay("My combatant is gone, so my guard is up"); + m_Mobile.Combatant = m_Mobile.FocusMob; + m_Mobile.FocusMob = null; + } + else if (!m_Mobile.InRange(combatant, m_Mobile.RangePerception * 3)) + { + m_Mobile.Combatant = null; + } + + combatant = m_Mobile.Combatant; + + if (combatant == null) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("My combatant has fled, so I am on guard"); + } + + Action = ActionType.Guard; + + return true; + } + } + + if (!MoveTo(combatant, true, m_Mobile.RangeFight)) + { + 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 {m_Mobile.FocusMob!.Name}"); + } + + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + + return true; + } + + if (m_Mobile.GetDistanceToSqrt(combatant) > m_Mobile.RangePerception + 1) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I cannot find {combatant.Name}, so my guard is up"); + } Action = ActionType.Guard; return true; } - if (!m_Mobile.InRange(combatant, m_Mobile.RangePerception)) + if (m_Mobile.Debug) { - // 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(combatant, m_Mobile.RangePerception * 3)) - { - m_Mobile.Combatant = null; - } - - combatant = m_Mobile.Combatant; - - if (combatant == null) - { - m_Mobile.DebugSay("My combatant has fled, so I am on guard"); - Action = ActionType.Guard; - - return true; - } + m_Mobile.DebugSay($"I cannot find {combatant.Name}, so my guard is up"); } + } - if (!MoveTo(combatant, true, m_Mobile.RangeFight)) + if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee) + { + if (m_Mobile.Hits < m_Mobile.HitsMax * 20 / 100) { - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + // We are low on health, should we flee? + + var fleeChance = 10 + Math.Max(0, combatant.Hits - m_Mobile.Hits); // (10 + diff)% chance to flee; + if (Utility.Random(0, 100) < fleeChance) { if (m_Mobile.Debug) { - m_Mobile.DebugSay("My move is blocked, so I am going to attack {0}", m_Mobile.FocusMob?.Name ?? "null"); + m_Mobile.DebugSay($"I am going to flee from {combatant.Name}"); } - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - - return true; - } - - if (m_Mobile.GetDistanceToSqrt(combatant) > m_Mobile.RangePerception + 1) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I cannot find {0}, so my guard is up", combatant.Name); - } - - Action = ActionType.Guard; - - return true; - } - - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I should be closer to {0}", combatant.Name); + Action = ActionType.Flee; } } - - 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? - - bool flee; - - if (m_Mobile.Hits < combatant.Hits) - { - // We are more hurt than them - - var diff = combatant.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}", combatant.Name); - } - - Action = ActionType.Flee; - } - } - } - - return true; } - public override bool DoActionGuard() + return true; + } + + public override bool DoActionGuard() + { + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + if (m_Mobile.Debug) { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I have detected {0}, attacking", m_Mobile.FocusMob.Name); - } - - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else - { - base.DoActionGuard(); + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name}, attacking"); } - return true; + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionGuard(); } - public override bool DoActionFlee() + return true; + } + + public override bool DoActionFlee() + { + if (m_Mobile.Hits > m_Mobile.HitsMax / 2) { - if (m_Mobile.Hits > m_Mobile.HitsMax / 2) + if (m_Mobile.Debug) { m_Mobile.DebugSay("I am stronger now, so I will continue fighting"); - Action = ActionType.Combat; - } - else - { - m_Mobile.FocusMob = m_Mobile.Combatant; - base.DoActionFlee(); } - return true; + Action = ActionType.Combat; } + else + { + m_Mobile.FocusMob = m_Mobile.Combatant; + base.DoActionFlee(); + } + + return true; } -} +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/PredatorAI.cs b/Projects/UOContent/Mobiles/AI/PredatorAI.cs index 8c4110293..bdfd5d890 100644 --- a/Projects/UOContent/Mobiles/AI/PredatorAI.cs +++ b/Projects/UOContent/Mobiles/AI/PredatorAI.cs @@ -1,82 +1,104 @@ -namespace Server.Mobiles -{ - public class PredatorAI : BaseAI - { - public PredatorAI(BaseCreature m) : base(m) - { - } +namespace Server.Mobiles; - public override bool DoActionWander() +public class PredatorAI : BaseAI +{ + public PredatorAI(BaseCreature m) : base(m) + { + } + + public override bool DoActionWander() + { + if (m_Mobile.Combatant != null) { - if (m_Mobile.Combatant != null) + if (m_Mobile.Debug) { m_Mobile.DebugSay("I am hurt or being attacked, I kill him"); - Action = ActionType.Combat; - } - else if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, true, false, true)) - { - m_Mobile.DebugSay("There is something near, I go away"); - Action = ActionType.Backoff; - } - else - { - base.DoActionWander(); } + Action = ActionType.Combat; + } + else if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, true, false, true)) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("There is something near, I go away"); + } + + Action = ActionType.Backoff; + } + else + { + base.DoActionWander(); + } + + return true; + } + + public override bool DoActionCombat() + { + var combatant = m_Mobile.Combatant; + + if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("My combatant is gone, so my guard is up"); + } + + Action = ActionType.Wander; return true; } - public override bool DoActionCombat() + if (!WalkMobileRange(combatant, 1, true, m_Mobile.RangeFight, m_Mobile.RangeFight)) { - var combatant = m_Mobile.Combatant; - - if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map) + if (m_Mobile.GetDistanceToSqrt(combatant) > m_Mobile.RangePerception + 1) { - m_Mobile.DebugSay("My combatant is gone, so my guard is up"); + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I cannot find {combatant.Name}"); + } + Action = ActionType.Wander; return true; } - if (!WalkMobileRange(combatant, 1, true, m_Mobile.RangeFight, m_Mobile.RangeFight)) + if (m_Mobile.Debug) { - if (m_Mobile.GetDistanceToSqrt(combatant) > m_Mobile.RangePerception + 1) - { - m_Mobile.DebugSay("I cannot find {0}", combatant.Name); - - Action = ActionType.Wander; - return true; - } - - m_Mobile.DebugSay("I should be closer to {0}", combatant.Name); + m_Mobile.DebugSay($"I should be closer to {combatant.Name}"); } - - return true; } - public override bool DoActionBackoff() - { - if (m_Mobile.IsHurt() || m_Mobile.Combatant != null) - { - Action = ActionType.Combat; - } - else - { - if (AcquireFocusMob(m_Mobile.RangePerception * 2, FightMode.Closest, true, false, true)) - { - if (WalkMobileRange(m_Mobile.FocusMob, 1, false, m_Mobile.RangePerception, m_Mobile.RangePerception * 2)) - { - m_Mobile.DebugSay("Well, here I am safe"); - Action = ActionType.Wander; - } - } - else - { - m_Mobile.DebugSay("I have lost my focus, lets relax"); - Action = ActionType.Wander; - } - } - - return true; - } + return true; } -} + + public override bool DoActionBackoff() + { + if (m_Mobile.IsHurt() || m_Mobile.Combatant != null) + { + Action = ActionType.Combat; + } + else if (AcquireFocusMob(m_Mobile.RangePerception * 2, FightMode.Closest, true, false, true)) + { + if (WalkMobileRange(m_Mobile.FocusMob, 1, false, m_Mobile.RangePerception, m_Mobile.RangePerception * 2)) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("Well, here I am safe"); + } + + Action = ActionType.Wander; + } + } + else + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay("I have lost my focus, lets relax"); + } + + Action = ActionType.Wander; + } + + return true; + } +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/ThiefAI.cs b/Projects/UOContent/Mobiles/AI/ThiefAI.cs index 71347cb66..486e905f2 100644 --- a/Projects/UOContent/Mobiles/AI/ThiefAI.cs +++ b/Projects/UOContent/Mobiles/AI/ThiefAI.cs @@ -1,179 +1,214 @@ +using System; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class ThiefAI : BaseAI { - public class ThiefAI : BaseAI + private Item m_toDisarm; + + public ThiefAI(BaseCreature m) : base(m) { - private Item m_toDisarm; + } - public ThiefAI(BaseCreature m) : base(m) - { - } - - public override bool DoActionWander() + public override bool DoActionWander() + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I have no combatant"); + } - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + { + if (m_Mobile.Debug) { - m_Mobile.DebugSay("I have detected {0}, attacking", m_Mobile.FocusMob.Name); + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name}, attacking"); + } - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionWander(); + } + + return true; + } + + public override bool DoActionCombat() + { + var combatant = m_Mobile.Combatant; + + if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map) + { + if (m_Mobile.Debug) { - base.DoActionWander(); + m_Mobile.DebugSay("My combatant is gone, so my guard is up"); } + Action = ActionType.Guard; + return true; } - public override bool DoActionCombat() + if (!WalkMobileRange(combatant, 1, true, m_Mobile.RangeFight, m_Mobile.RangeFight)) { - var combatant = m_Mobile.Combatant; - - if (combatant?.Deleted != false || combatant.Map != m_Mobile.Map) + if (m_Mobile.Debug) { - m_Mobile.DebugSay("My combatant is gone, so my guard is up"); - - Action = ActionType.Guard; - - return true; + m_Mobile.DebugSay($"I should be closer to {combatant.Name}"); + } + } + else + { + if (m_toDisarm?.IsChildOf(m_Mobile.Backpack) != false) + { + m_toDisarm = combatant.FindItemOnLayer(Layer.OneHanded) ?? combatant.FindItemOnLayer(Layer.TwoHanded); } - if (!WalkMobileRange(combatant, 1, true, m_Mobile.RangeFight, m_Mobile.RangeFight)) + if (!Core.AOS && !m_Mobile.DisarmReady && m_Mobile.Skills.Wrestling.Value >= 80.0 && + m_Mobile.Skills.ArmsLore.Value >= 80.0 && m_toDisarm != null) { - m_Mobile.DebugSay("I should be closer to {0}", combatant.Name); + EventSink.InvokeDisarmRequest(m_Mobile); } - else + + if (m_toDisarm?.IsChildOf(combatant.Backpack) == true && + Core.TickCount - m_Mobile.NextSkillTime >= 0 && m_toDisarm.LootType != LootType.Blessed && + m_toDisarm.LootType != LootType.Newbied) { - if (m_toDisarm?.IsChildOf(m_Mobile.Backpack) != false) - { - m_toDisarm = combatant.FindItemOnLayer(Layer.OneHanded) ?? combatant.FindItemOnLayer(Layer.TwoHanded); - } - - if (!Core.AOS && !m_Mobile.DisarmReady && m_Mobile.Skills.Wrestling.Value >= 80.0 && - m_Mobile.Skills.ArmsLore.Value >= 80.0 && m_toDisarm != null) - { - EventSink.InvokeDisarmRequest(m_Mobile); - } - - if (m_toDisarm?.IsChildOf(combatant.Backpack) == true && - Core.TickCount - m_Mobile.NextSkillTime >= 0 && m_toDisarm.LootType != LootType.Blessed && - m_toDisarm.LootType != LootType.Newbied) + if (m_Mobile.Debug) { m_Mobile.DebugSay("Trying to steal from combatant."); - m_Mobile.UseSkill(SkillName.Stealing); - m_Mobile.Target?.Invoke(m_Mobile, m_toDisarm); } - else if (m_toDisarm == null && Core.TickCount - m_Mobile.NextSkillTime >= 0) + + m_Mobile.UseSkill(SkillName.Stealing); + m_Mobile.Target?.Invoke(m_Mobile, m_toDisarm); + } + else if (m_toDisarm == null && Core.TickCount - m_Mobile.NextSkillTime >= 0) + { + var cpack = combatant.Backpack; + + if (cpack != null) { - var cpack = combatant.Backpack; - - if (cpack != null) + Item steala = cpack.FindItemByType(); + if (steala != null) { - Item steala = cpack.FindItemByType(); - if (steala != null) + if (m_Mobile.Debug) { m_Mobile.DebugSay("Trying to steal from combatant."); - m_Mobile.UseSkill(SkillName.Stealing); - m_Mobile.Target?.Invoke(m_Mobile, steala); } - Item stealb = cpack.FindItemByType(); - if (stealb != null) + m_Mobile.UseSkill(SkillName.Stealing); + m_Mobile.Target?.Invoke(m_Mobile, steala); + } + + Item stealb = cpack.FindItemByType(); + if (stealb != null) + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("Trying to steal from combatant."); - m_Mobile.UseSkill(SkillName.Stealing); - m_Mobile.Target?.Invoke(m_Mobile, stealb); } - Item stealc = cpack.FindItemByType(); - if (stealc != null) + m_Mobile.UseSkill(SkillName.Stealing); + m_Mobile.Target?.Invoke(m_Mobile, stealb); + } + + Item stealc = cpack.FindItemByType(); + if (stealc != null) + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("Trying to steal from combatant."); - m_Mobile.UseSkill(SkillName.Stealing); - m_Mobile.Target?.Invoke(m_Mobile, stealc); } - Item steald = cpack.FindItemByType(); - if (steald != null) + m_Mobile.UseSkill(SkillName.Stealing); + m_Mobile.Target?.Invoke(m_Mobile, stealc); + } + + Item steald = cpack.FindItemByType(); + if (steald != null) + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("Trying to steal from combatant."); - m_Mobile.UseSkill(SkillName.Stealing); - m_Mobile.Target?.Invoke(m_Mobile, steald); } - else if (steala == null && stealb == null && stealc == null) - { - m_Mobile.DebugSay("I am going to flee from {0}", combatant.Name); - Action = ActionType.Flee; + m_Mobile.UseSkill(SkillName.Stealing); + m_Mobile.Target?.Invoke(m_Mobile, steald); + } + else if (steala == null && stealb == null && stealc == null) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I am going to flee from {combatant.Name}"); } + + Action = ActionType.Flee; } } } + } - if (m_Mobile.Hits >= m_Mobile.HitsMax * 20 / 100 || !m_Mobile.CanFlee) - { - return true; - } - - // We are low on health, should we flee? - bool flee; - - if (m_Mobile.Hits < combatant.Hits) - { - // We are more hurt than them - var diff = combatant.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) - { - m_Mobile.DebugSay("I am going to flee from {0}", combatant.Name); - Action = ActionType.Flee; - } - + if (m_Mobile.Hits >= m_Mobile.HitsMax * 20 / 100 || !m_Mobile.CanFlee) + { return true; } - public override bool DoActionGuard() + // We are low on health, should we flee? + // (10 + diff)% chance to flee + var fleeChance = 10 + Math.Max(0, combatant.Hits - m_Mobile.Hits); + + if (Utility.Random(0, 100) > fleeChance) { - if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) + if (m_Mobile.Debug) { - m_Mobile.DebugSay("I have detected {0}, attacking", m_Mobile.FocusMob.Name); - - m_Mobile.Combatant = m_Mobile.FocusMob; - Action = ActionType.Combat; - } - else - { - base.DoActionGuard(); + m_Mobile.DebugSay($"I am going to flee from {combatant.Name}"); } - return true; + Action = ActionType.Flee; } - public override bool DoActionFlee() + return true; + } + + public override bool DoActionGuard() + { + if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true)) { - if (m_Mobile.Hits > m_Mobile.HitsMax / 2) + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"I have detected {m_Mobile.FocusMob.Name}, attacking"); + } + + m_Mobile.Combatant = m_Mobile.FocusMob; + Action = ActionType.Combat; + } + else + { + base.DoActionGuard(); + } + + return true; + } + + public override bool DoActionFlee() + { + if (m_Mobile.Hits > m_Mobile.HitsMax / 2) + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I am stronger now, so I will continue fighting"); - Action = ActionType.Combat; - } - else - { - m_Mobile.FocusMob = m_Mobile.Combatant; - base.DoActionFlee(); } - return true; + Action = ActionType.Combat; } + else + { + m_Mobile.FocusMob = m_Mobile.Combatant; + base.DoActionFlee(); + } + + return true; } -} +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/AI/VendorAI.cs b/Projects/UOContent/Mobiles/AI/VendorAI.cs index ade3686e5..066be44e3 100644 --- a/Projects/UOContent/Mobiles/AI/VendorAI.cs +++ b/Projects/UOContent/Mobiles/AI/VendorAI.cs @@ -1,157 +1,158 @@ -namespace Server.Mobiles -{ - public class VendorAI : BaseAI - { - public VendorAI(BaseCreature m) : base(m) - { - } +namespace Server.Mobiles; - public override bool DoActionWander() +public class VendorAI : BaseAI +{ + public VendorAI(BaseCreature m) : base(m) + { + } + + public override bool DoActionWander() + { + if (m_Mobile.Debug) { m_Mobile.DebugSay("I'm fine"); + } - if (m_Mobile.Combatant != null) + if (m_Mobile.Combatant != null) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"{m_Mobile.Combatant.Name} is attacking me"); + } + + m_Mobile.Say(Utility.RandomBool() ? 1005305 : 501603); + Action = ActionType.Flee; + } + else + { + if (m_Mobile.FocusMob != null) { if (m_Mobile.Debug) { - m_Mobile.DebugSay("{0} is attacking me", m_Mobile.Combatant.Name); + m_Mobile.DebugSay($"{m_Mobile.FocusMob.Name} has talked to me"); } - m_Mobile.Say(Utility.RandomList(1005305, 501603)); - - Action = ActionType.Flee; + Action = ActionType.Interact; } else { - if (m_Mobile.FocusMob != null) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("{0} has talked to me", m_Mobile.FocusMob.Name); - } + m_Mobile.Warmode = false; - Action = ActionType.Interact; - } - else - { - m_Mobile.Warmode = false; - - base.DoActionWander(); - } + base.DoActionWander(); } + } + + return true; + } + + public override bool DoActionInteract() + { + var customer = m_Mobile.FocusMob; + + if (m_Mobile.Combatant != null) + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"{m_Mobile.Combatant.Name} is attacking me"); + } + + m_Mobile.Say(Utility.RandomList(1005305, 501603)); + + Action = ActionType.Flee; return true; } - public override bool DoActionInteract() + if (customer?.Deleted != false || customer.Map != m_Mobile.Map) { - var customer = m_Mobile.FocusMob; - - if (m_Mobile.Combatant != null) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("{0} is attacking me", m_Mobile.Combatant.Name); - } - - m_Mobile.Say(Utility.RandomList(1005305, 501603)); - - Action = ActionType.Flee; - - return true; - } - - if (customer?.Deleted != false || customer.Map != m_Mobile.Map) + if (m_Mobile.Debug) { m_Mobile.DebugSay("My customer have disapeared"); - m_Mobile.FocusMob = null; - - Action = ActionType.Wander; } - else + + m_Mobile.FocusMob = null; + + Action = ActionType.Wander; + } + else if (customer.InRange(m_Mobile, m_Mobile.RangeFight)) + { + if (m_Mobile.Debug) { - if (customer.InRange(m_Mobile, m_Mobile.RangeFight)) - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("I am with {0}", customer.Name); - } - - m_Mobile.Direction = m_Mobile.GetDirectionTo(customer); - } - else - { - if (m_Mobile.Debug) - { - m_Mobile.DebugSay("{0} is gone", customer.Name); - } - - m_Mobile.FocusMob = null; - - Action = ActionType.Wander; - } + m_Mobile.DebugSay($"I am with {customer.Name}"); } + m_Mobile.Direction = m_Mobile.GetDirectionTo(customer); + } + else + { + if (m_Mobile.Debug) + { + m_Mobile.DebugSay($"{customer.Name} is gone"); + } + + m_Mobile.FocusMob = null; + Action = ActionType.Wander; + } + + return true; + } + + public override bool DoActionGuard() + { + m_Mobile.FocusMob = m_Mobile.Combatant; + return base.DoActionGuard(); + } + + public override bool HandlesOnSpeech(Mobile from) + { + if (from.InRange(m_Mobile, 4)) + { return true; } - public override bool DoActionGuard() - { - m_Mobile.FocusMob = m_Mobile.Combatant; - return base.DoActionGuard(); - } + return base.HandlesOnSpeech(from); + } - public override bool HandlesOnSpeech(Mobile from) + // Temporary + public override void OnSpeech(SpeechEventArgs e) + { + base.OnSpeech(e); + + var from = e.Mobile; + + if (m_Mobile is BaseVendor vendor && from.InRange(m_Mobile, Core.AOS ? 1 : 4) && !e.Handled) { - if (from.InRange(m_Mobile, 4)) + if (e.HasKeyword(0x14D)) // *vendor sell* { - return true; + e.Handled = true; + + vendor.VendorSell(from); + vendor.FocusMob = from; } - - return base.HandlesOnSpeech(from); - } - - // Temporary - public override void OnSpeech(SpeechEventArgs e) - { - base.OnSpeech(e); - - var from = e.Mobile; - - if (m_Mobile is BaseVendor vendor && from.InRange(m_Mobile, Core.AOS ? 1 : 4) && !e.Handled) + else if (e.HasKeyword(0x3C)) // *vendor buy* { - if (e.HasKeyword(0x14D)) // *vendor sell* + e.Handled = true; + + vendor.VendorBuy(from); + vendor.FocusMob = from; + } + else if (WasNamed(e.Speech)) + { + if (e.HasKeyword(0x177)) // *sell* { e.Handled = true; vendor.VendorSell(from); - vendor.FocusMob = from; } - else if (e.HasKeyword(0x3C)) // *vendor buy* + else if (e.HasKeyword(0x171)) // *buy* { e.Handled = true; vendor.VendorBuy(from); - vendor.FocusMob = from; } - else if (WasNamed(e.Speech)) - { - if (e.HasKeyword(0x177)) // *sell* - { - e.Handled = true; - vendor.VendorSell(from); - } - else if (e.HasKeyword(0x171)) // *buy* - { - e.Handled = true; - - vendor.VendorBuy(from); - } - - vendor.FocusMob = from; - } + vendor.FocusMob = from; } } } -} +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index d5127a6ab..25e2a90a1 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Server.Collections; using Server.ContextMenus; using Server.Engines.ConPVP; @@ -2271,20 +2272,11 @@ namespace Server.Mobiles base.OnAfterDelete(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void DebugSay(string text) { - if (Debug) - { - PublicOverheadMessage(MessageType.Regular, 41, false, text); - } - } - - public void DebugSay(string format, params object[] args) - { - if (Debug) - { - PublicOverheadMessage(MessageType.Regular, 41, false, string.Format(format, args)); - } + // Moved the debug check to implementation layer so we can avoid string formatting when we do not need it + PublicOverheadMessage(MessageType.Regular, 41, false, text); } /*