diff --git a/Projects/UOContent.Tests/Tests/Mobiles/AI/PetOrderTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/AI/PetOrderTests.cs index e0fe22a39..e5d7760ed 100644 --- a/Projects/UOContent.Tests/Tests/Mobiles/AI/PetOrderTests.cs +++ b/Projects/UOContent.Tests/Tests/Mobiles/AI/PetOrderTests.cs @@ -176,6 +176,38 @@ public class PetOrderTests : IDisposable Assert.Equal(Direction.North, pet.Direction); // frozen -> no wander attempts } + [Fact] + public void ReleaseOrder_ClearsTheMaster_AndStartsTheDeleteCountdown() + { + var (master, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0)); + pet.IsBonded = true; + var followers = master.Followers; + + pet.ControlOrder = OrderType.Release; + + Assert.False(pet.Controlled); + Assert.Null(pet.ControlMaster); + Assert.False(pet.IsBonded); + Assert.Equal(followers - pet.ControlSlots, master.Followers); + Assert.True(pet.PendingDeleteTimer?.Running); + Assert.Equal(pet.Location, pet.Home); + } + + [Fact] + public void LoyaltyRelease_ClearsTheMaster_AndStartsTheDeleteCountdown() + { + var (master, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0)); + var followers = master.Followers; + + // What the loyalty drain calls when loyalty reaches zero. + pet.AIObject.DoOrderRelease(); + + Assert.False(pet.Controlled); + Assert.Null(pet.ControlMaster); + Assert.Equal(followers - pet.ControlSlots, master.Followers); + Assert.True(pet.PendingDeleteTimer?.Running); + } + [Fact] public void Release_WithoutSpawner_AnchorsHomeToCurrentLocation() { diff --git a/Projects/UOContent/Engines/Factions/Gumps/HorseBreederGump.cs b/Projects/UOContent/Engines/Factions/Gumps/HorseBreederGump.cs index 7cd4edc92..8e8bf9308 100644 --- a/Projects/UOContent/Engines/Factions/Gumps/HorseBreederGump.cs +++ b/Projects/UOContent/Engines/Factions/Gumps/HorseBreederGump.cs @@ -61,7 +61,7 @@ public class HorseBreederGump : FactionGump if (m_From.Followers + horse.ControlSlots > m_From.FollowersMax) { - // TODO: Message? + m_From.SendLocalizedMessage(1049607); // You have too many followers to control that creature. horse.Delete(); } else @@ -79,9 +79,7 @@ public class HorseBreederGump : FactionGump else if (pack.ConsumeTotal(typeof(Silver), FactionWarHorse.SilverPrice) && pack.ConsumeTotal(typeof(Gold), FactionWarHorse.GoldPrice)) { - horse.Controlled = true; - horse.ControlMaster = m_From; - + horse.SetControlMaster(m_From); horse.ControlOrder = OrderType.Follow; horse.ControlTarget = m_From; diff --git a/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs b/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs index 070a802b1..bf0953041 100644 --- a/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs +++ b/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs @@ -228,7 +228,7 @@ public partial class BallOfSummoning : Item, TranslocationItem { pet.SetControlMaster(from); - if (pet.Summoned) + if (pet.SummonMaster != null) { pet.SummonMaster = from; } diff --git a/Projects/UOContent/Misc/Notoriety.cs b/Projects/UOContent/Misc/Notoriety.cs index cb9b1ddcb..4416b09b5 100644 --- a/Projects/UOContent/Misc/Notoriety.cs +++ b/Projects/UOContent/Misc/Notoriety.cs @@ -228,7 +228,7 @@ namespace Server.Misc } if (bcTarg?.Controlled == true - || bcTarg?.Summoned == true && bcTarg.SummonMaster != from && bcTarg.SummonMaster.Player) + || bcTarg?.Summoned == true && bcTarg.SummonMaster != from && bcTarg.SummonMaster?.Player == true) { return false; // Cannot harm other controlled mobiles from players } diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs b/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs index 8ffd39b42..ce9af5bb7 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs @@ -448,7 +448,7 @@ public abstract partial class BaseAI { Mobile.SetControlMaster(e.Mobile); - if (Mobile.Summoned) + if (Mobile.SummonMaster != null) { Mobile.SummonMaster = e.Mobile; } diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/PetOrderHandlers.cs b/Projects/UOContent/Mobiles/AI/BaseAI/PetOrderHandlers.cs index 862c78649..a132bc896 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI/PetOrderHandlers.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI/PetOrderHandlers.cs @@ -261,15 +261,8 @@ public abstract partial class BaseAI } _commandIssuer?.RevealingAction(); - Mobile.ControlTarget = null; - Mobile.FocusMob = null; - Mobile.Warmode = false; - Mobile.Combatant = null; Mobile.PlaySound(Mobile.GetIdleSound()); - Mobile.BondingBegin = DateTime.MinValue; - Mobile.OwnerAbandonTime = DateTime.MinValue; - Mobile.IsBonded = false; - Mobile.SetControlMaster(null); + DoOrderRelease(); _commandIssuer = null; } diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs b/Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs index e6c48850d..e09424328 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI/PetOrders.cs @@ -13,6 +13,8 @@ * along with this program. If not, see . * ************************************************************************/ +using System; + namespace Server.Mobiles; public abstract partial class BaseAI @@ -457,10 +459,23 @@ public abstract partial class BaseAI return best; } + /// + /// The whole release: the master is cleared here, so this runs once, synchronously, from + /// the Release order handler or the loyalty drain, never from Obey. + /// public virtual bool DoOrderRelease() { DebugSay("I have been released to the wild."); + Mobile.ControlTarget = null; + Mobile.FocusMob = null; + Mobile.Warmode = false; + Mobile.Combatant = null; + Mobile.BondingBegin = DateTime.MinValue; + Mobile.OwnerAbandonTime = DateTime.MinValue; + Mobile.IsBonded = false; + Mobile.SetControlMaster(null); + var spawner = Mobile.Spawner; if (spawner != null) diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs b/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs index 9efad5a5e..331df533c 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs @@ -156,7 +156,7 @@ internal sealed partial class TransferItem : Item private void TransferPetOwnership(Mobile from, Mobile to) { - if (_creature.Summoned) + if (_creature.SummonMaster != null) { _creature.SummonMaster = to; } diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 5ea68cb13..f8dba7bf1 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -5923,14 +5923,7 @@ namespace Server.Mobiles c.Say(1043255, c.Name); // ~1_NAME~ appears to have decided that is better off without a master! c.Loyalty = BaseCreature.MaxLoyalty; - c.IsBonded = false; - c.BondingBegin = DateTime.MinValue; - c.OwnerAbandonTime = DateTime.MinValue; - c.ControlTarget = null; - // Release directly: a creature left alone with its AI disabled would - // otherwise never release and permanently hold its owner's follower slots. c.AIObject.DoOrderRelease(); - c.DropBackpack(); } while (toRemove.Count > 0) diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index d0b1b2c7a..39be0e10a 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -3602,7 +3602,7 @@ namespace Server.Mobiles { pet.SetControlMaster(this); - if (pet.Summoned) + if (pet.SummonMaster != null) { pet.SummonMaster = this; } diff --git a/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs b/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs index 847209869..b3fb4d721 100644 --- a/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs +++ b/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs @@ -357,7 +357,7 @@ namespace Server.Mobiles { pet.SetControlMaster(from); - if (pet.Summoned) + if (pet.SummonMaster != null) { pet.SummonMaster = from; } diff --git a/Projects/UOContent/Systems/JailSystem/JailSystem.cs b/Projects/UOContent/Systems/JailSystem/JailSystem.cs index 8802d0487..8da7319c9 100644 --- a/Projects/UOContent/Systems/JailSystem/JailSystem.cs +++ b/Projects/UOContent/Systems/JailSystem/JailSystem.cs @@ -144,6 +144,7 @@ public class JailSystem : GenericPersistence bc.Internalize(); bc.SetControlMaster(null); + bc.SummonMaster = null; bc.IsStabled = true; bc.StabledBy = from;