From a082202e98096a851cc1213340093b70d1823849 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 23 Aug 2026 20:07:35 -0700 Subject: [PATCH] refactor: serialize one master reference; fold the SummonMaster lockstep ControlMaster and SummonMaster, when both set, are always the same mobile: BaseCreature.Summon assigns both to the caster, and every pet management flow (transfer, stable, claim, ball of summoning, GM obey, login overflow) followed SetControlMaster with an identical SummonMaster assignment. They differ only in presence - uncontrolled summons carry only a summon master, pets only a control master. So one _master reference serializes (refreshed at save, fanned back out through the Controlled/Summoned flags in AfterDeserialization; legacy loads feed the same path), and SetControlMaster now keeps SummonMaster in lockstep itself, deleting the six hand-rolled copies of that boilerplate. Also: a creature constructed without speeds (missing npc-speeds.json) now logs debug and defaults to Medium (0.25/0.5) instead of throwing - this is the place a sane default belongs. Co-Authored-By: Claude Fable 5 --- .../Mobiles/BaseCreatureSerializationTests.cs | 21 +++ .../Special/Solen Items/BallOfSummoning.cs | 6 - .../Server.Mobiles.BaseCreature.v23.json | 8 +- .../UOContent/Mobiles/AI/BaseAI/OnSpeech.cs | 5 - .../Mobiles/AI/BaseAI/TransferItem.cs | 5 - Projects/UOContent/Mobiles/BaseCreature.cs | 134 +++++++++++------- Projects/UOContent/Mobiles/PlayerMobile.cs | 7 - .../Mobiles/Vendors/NPC/AnimalTrainer.cs | 7 - 8 files changed, 102 insertions(+), 91 deletions(-) diff --git a/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs index 11dec7073..724b14070 100644 --- a/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs +++ b/Projects/UOContent.Tests/Tests/Mobiles/BaseCreatureSerializationTests.cs @@ -164,6 +164,27 @@ public class BaseCreatureSerializationTests : IDisposable Assert.Equal(master, copy.LastOwner); } + [Fact] + public void UncontrolledSummon_KeepsItsSummonMaster() + { + var bc = NewCreature(); + var master = new PlayerMobile(World.NewMobile); + master.DefaultMobileInit(); + World.AddEntity(master); + _created.Add(master); + + // Energy vortex-style: summoned with a master, never controlled. + bc.Summoned = true; + bc.SummonMaster = master; + + var copy = Load(Snapshot(bc)); + + Assert.True(copy.Summoned); + Assert.False(copy.Controlled); + Assert.Equal(master, copy.SummonMaster); + Assert.Null(copy.ControlMaster); + } + private sealed class BucketStub : BaseCreature { public BucketStub() : base(AIType.AI_Melee) => Body = 0xC9; diff --git a/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs b/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs index 070a802b1..3936d68db 100644 --- a/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs +++ b/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs @@ -227,12 +227,6 @@ public partial class BallOfSummoning : Item, TranslocationItem if (pet.IsStabled) { pet.SetControlMaster(from); - - if (pet.Summoned) - { - pet.SummonMaster = from; - } - pet.ControlTarget = from; pet.ControlOrder = OrderType.Follow; diff --git a/Projects/UOContent/Migrations/Server.Mobiles.BaseCreature.v23.json b/Projects/UOContent/Migrations/Server.Mobiles.BaseCreature.v23.json index 8fab5fb7a..077a428fe 100644 --- a/Projects/UOContent/Migrations/Server.Mobiles.BaseCreature.v23.json +++ b/Projects/UOContent/Migrations/Server.Mobiles.BaseCreature.v23.json @@ -133,12 +133,6 @@ "" ] }, - { - "name": "ControlMaster", - "type": "Server.Mobile", - "usesSaveFlag": true, - "rule": "SerializableInterfaceMigrationRule" - }, { "name": "ControlTarget", "type": "Server.Mobile", @@ -197,7 +191,7 @@ ] }, { - "name": "SummonMaster", + "name": "Master", "type": "Server.Mobile", "usesSaveFlag": true, "rule": "SerializableInterfaceMigrationRule" diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs b/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs index 8ffd39b42..f254b68ae 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI/OnSpeech.cs @@ -447,11 +447,6 @@ public abstract partial class BaseAI if (Mobile.FindMyName(e.Speech, true) && e.Speech.InsensitiveContains("obey")) { Mobile.SetControlMaster(e.Mobile); - - if (Mobile.Summoned) - { - Mobile.SummonMaster = e.Mobile; - } } } } diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs b/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs index 9efad5a5e..9f2fa937c 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI/TransferItem.cs @@ -156,11 +156,6 @@ internal sealed partial class TransferItem : Item private void TransferPetOwnership(Mobile from, Mobile to) { - if (_creature.Summoned) - { - _creature.SummonMaster = to; - } - _creature.ControlTarget = to; _creature.ControlOrder = OrderType.Follow; _creature.BondingBegin = DateTime.MinValue; diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 7c3ff0f66..67da2b526 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -13,6 +13,7 @@ using Server.Engines.Virtues; using Server.Ethics; using Server.Factions; using Server.Items; +using Server.Logging; using Server.Misc; using Server.Multis; using Server.Network; @@ -136,6 +137,8 @@ namespace Server.Mobiles [SerializationGenerator(23, false)] public abstract partial class BaseCreature : Mobile, IHonorTarget, IQuestGiver { + private static readonly ILogger logger = LogFactory.GetLogger(typeof(BaseCreature)); + public enum Allegiance { None, @@ -491,43 +494,41 @@ namespace Server.Mobiles InvalidateProperties(); } - // Field 16: ControlMaster (hand-written property; follower bookkeeping brackets the assignment) + // ControlMaster and SummonMaster serialize as one master reference (Master below). private Mobile _controlMaster; - private bool ShouldSerializeControlMaster() => _controlMaster != null; - - [SerializableField(17)] + [SerializableField(16)] [SaveFlag(nameof(ShouldSerializeControlTarget))] [SerializedCommandProperty(AccessLevel.GameMaster)] private Mobile _controlTarget; private bool ShouldSerializeControlTarget() => _controlTarget != null; - [SerializableField(18)] + [SerializableField(17)] [SaveFlag(nameof(ShouldSerializeControlDest))] [SerializedCommandProperty(AccessLevel.GameMaster)] private Point3D _controlDest; private bool ShouldSerializeControlDest() => _controlDest != Point3D.Zero; - // Field 19: ControlOrder (hand-written property; order logic must run on equal re-assignment) + // Field 18: ControlOrder (hand-written property; order logic must run on equal re-assignment) private OrderType _controlOrder; private bool ShouldSerializeControlOrder() => _controlOrder != OrderType.None; - [SerializableField(20)] + [SerializableField(19)] [SaveFlag(nameof(ShouldSerializeMinTameSkill))] [SerializedCommandProperty(AccessLevel.GameMaster)] private double _minTameSkill; private bool ShouldSerializeMinTameSkill() => _minTameSkill != 0; - // Field 21: Tamable (hand-written property; custom getter masks paragons) + // Field 20: Tamable (hand-written property; custom getter masks paragons) private bool _tamable; private bool ShouldSerializeTamable() => _tamable; - [SerializableField(22, fieldChanged: nameof(OnSummonedChange))] + [SerializableField(21, fieldChanged: nameof(OnSummonedChange))] [SaveFlag(nameof(ShouldSerializeSummoned))] [SerializedCommandProperty(AccessLevel.Administrator)] private bool _summoned; @@ -542,19 +543,30 @@ namespace Server.Mobiles } [AnchoredDateTime] - [SerializableField(23, getter: "protected", setter: "protected")] + [SerializableField(22, getter: "protected", setter: "protected")] [SaveFlag(nameof(ShouldSerializeSummonEnd))] private DateTime _summonEnd; private bool ShouldSerializeSummonEnd() => _summoned; - // Field 24: SummonMaster (hand-written property; follower bookkeeping brackets the assignment) private Mobile _summonMaster; - private bool ShouldSerializeSummonMaster() => _summonMaster != null; + // When both roles are set they are always the same mobile (every management flow + // assigns them in lockstep via SetControlMaster), so one reference serializes - + // refreshed here at save - and fans back out through the Controlled/Summoned + // flags in AfterDeserialization. + [SerializableField(23, getter: "private", setter: "private")] + [SaveFlag(nameof(ShouldSerializeMaster))] + private Mobile _master; + + private bool ShouldSerializeMaster() + { + _master = _controlMaster ?? _summonMaster; + return _master != null; + } [EncodedInt] - [SerializableField(25)] + [SerializableField(24)] [SaveFlag(nameof(ShouldSerializeControlSlots), nameof(ControlSlotsDefaultValue))] [SerializedCommandProperty(AccessLevel.Administrator)] private int _controlSlots = 1; @@ -564,7 +576,7 @@ namespace Server.Mobiles private int ControlSlotsDefaultValue() => 1; [EncodedInt] - [SerializableField(26, allowFieldChange: nameof(ClampLoyalty))] + [SerializableField(25, allowFieldChange: nameof(ClampLoyalty))] [SaveFlag(nameof(ShouldSerializeLoyalty), nameof(LoyaltyDefaultValue))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _loyalty; @@ -579,7 +591,7 @@ namespace Server.Mobiles return true; } - [SerializableField(27)] + [SerializableField(26)] [SaveFlag(nameof(ShouldSerializeCurrentWayPoint))] [SerializedCommandProperty(AccessLevel.GameMaster)] private WayPoint _currentWayPoint; @@ -587,7 +599,7 @@ namespace Server.Mobiles private bool ShouldSerializeCurrentWayPoint() => _currentWayPoint != null; [EncodedInt] - [SerializableField(28)] + [SerializableField(27)] [SaveFlag(nameof(ShouldSerializeHitsMaxSeed), nameof(HitsMaxSeedDefaultValue))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _hitsMaxSeed = -1; @@ -597,7 +609,7 @@ namespace Server.Mobiles private int HitsMaxSeedDefaultValue() => -1; [EncodedInt] - [SerializableField(29)] + [SerializableField(28)] [SaveFlag(nameof(ShouldSerializeStamMaxSeed), nameof(StamMaxSeedDefaultValue))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _stamMaxSeed = -1; @@ -607,7 +619,7 @@ namespace Server.Mobiles private int StamMaxSeedDefaultValue() => -1; [EncodedInt] - [SerializableField(30)] + [SerializableField(29)] [SaveFlag(nameof(ShouldSerializeManaMaxSeed), nameof(ManaMaxSeedDefaultValue))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _manaMaxSeed = -1; @@ -617,7 +629,7 @@ namespace Server.Mobiles private int ManaMaxSeedDefaultValue() => -1; [EncodedInt] - [SerializableField(31)] + [SerializableField(30)] [SaveFlag(nameof(ShouldSerializeDamageMin), nameof(DamageMinDefaultValue))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _damageMin = -1; @@ -627,7 +639,7 @@ namespace Server.Mobiles private int DamageMinDefaultValue() => -1; [EncodedInt] - [SerializableField(32)] + [SerializableField(31)] [SaveFlag(nameof(ShouldSerializeDamageMax), nameof(DamageMaxDefaultValue))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _damageMax = -1; @@ -637,7 +649,7 @@ namespace Server.Mobiles private int DamageMaxDefaultValue() => -1; [EncodedInt] - [SerializableField(33, fieldChanged: nameof(OnResistanceSeedChange))] + [SerializableField(32, fieldChanged: nameof(OnResistanceSeedChange))] [SaveFlag(nameof(ShouldSerializePhysicalResistanceSeed))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _physicalResistanceSeed; @@ -647,7 +659,7 @@ namespace Server.Mobiles private void OnResistanceSeedChange(int oldValue, int newValue) => UpdateResistances(); [EncodedInt] - [SerializableField(34, fieldChanged: nameof(OnResistanceSeedChange))] + [SerializableField(33, fieldChanged: nameof(OnResistanceSeedChange))] [SaveFlag(nameof(ShouldSerializeFireResistSeed))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _fireResistSeed; @@ -655,7 +667,7 @@ namespace Server.Mobiles private bool ShouldSerializeFireResistSeed() => _fireResistSeed != 0; [EncodedInt] - [SerializableField(35, fieldChanged: nameof(OnResistanceSeedChange))] + [SerializableField(34, fieldChanged: nameof(OnResistanceSeedChange))] [SaveFlag(nameof(ShouldSerializeColdResistSeed))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _coldResistSeed; @@ -663,7 +675,7 @@ namespace Server.Mobiles private bool ShouldSerializeColdResistSeed() => _coldResistSeed != 0; [EncodedInt] - [SerializableField(36, fieldChanged: nameof(OnResistanceSeedChange))] + [SerializableField(35, fieldChanged: nameof(OnResistanceSeedChange))] [SaveFlag(nameof(ShouldSerializePoisonResistSeed))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _poisonResistSeed; @@ -671,7 +683,7 @@ namespace Server.Mobiles private bool ShouldSerializePoisonResistSeed() => _poisonResistSeed != 0; [EncodedInt] - [SerializableField(37, fieldChanged: nameof(OnResistanceSeedChange))] + [SerializableField(36, fieldChanged: nameof(OnResistanceSeedChange))] [SaveFlag(nameof(ShouldSerializeEnergyResistSeed))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _energyResistSeed; @@ -679,7 +691,7 @@ namespace Server.Mobiles private bool ShouldSerializeEnergyResistSeed() => _energyResistSeed != 0; [EncodedInt] - [SerializableField(38)] + [SerializableField(37)] [SaveFlag(nameof(ShouldSerializePhysicalDamage), nameof(PhysicalDamageDefaultValue))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _physicalDamage = 100; @@ -689,7 +701,7 @@ namespace Server.Mobiles private int PhysicalDamageDefaultValue() => 100; [EncodedInt] - [SerializableField(39)] + [SerializableField(38)] [SaveFlag(nameof(ShouldSerializeFireDamage))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _fireDamage; @@ -697,7 +709,7 @@ namespace Server.Mobiles private bool ShouldSerializeFireDamage() => _fireDamage != 0; [EncodedInt] - [SerializableField(40)] + [SerializableField(39)] [SaveFlag(nameof(ShouldSerializeColdDamage))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _coldDamage; @@ -705,7 +717,7 @@ namespace Server.Mobiles private bool ShouldSerializeColdDamage() => _coldDamage != 0; [EncodedInt] - [SerializableField(41)] + [SerializableField(40)] [SaveFlag(nameof(ShouldSerializePoisonDamage))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _poisonDamage; @@ -713,7 +725,7 @@ namespace Server.Mobiles private bool ShouldSerializePoisonDamage() => _poisonDamage != 0; [EncodedInt] - [SerializableField(42)] + [SerializableField(41)] [SaveFlag(nameof(ShouldSerializeEnergyDamage))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _energyDamage; @@ -721,7 +733,7 @@ namespace Server.Mobiles private bool ShouldSerializeEnergyDamage() => _energyDamage != 0; [Tidy] - [SerializableField(43, setter: "private")] + [SerializableField(42, setter: "private")] [SaveFlag(nameof(ShouldSerializeOwners), nameof(OwnersDefaultValue))] private List _owners; @@ -733,13 +745,13 @@ namespace Server.Mobiles private List OwnersDefaultValue() => new(); - [SerializableField(44)] + [SerializableField(43)] [SaveFlag(nameof(ShouldSerializeIsDeadPet))] private bool _isDeadPet; private bool ShouldSerializeIsDeadPet() => _isDeadPet; - [SerializableField(45, fieldChanged: nameof(OnBondedChange))] + [SerializableField(44, fieldChanged: nameof(OnBondedChange))] [SaveFlag(nameof(ShouldSerializeIsBonded))] [SerializedCommandProperty(AccessLevel.GameMaster)] private bool _isBonded; @@ -748,33 +760,33 @@ namespace Server.Mobiles private void OnBondedChange(bool oldValue, bool newValue) => InvalidateProperties(); - [SerializableField(46)] + [SerializableField(45)] [SaveFlag(nameof(ShouldSerializeBondingBegin))] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _bondingBegin; private bool ShouldSerializeBondingBegin() => _bondingBegin != DateTime.MinValue; - [SerializableField(47)] + [SerializableField(46)] [SaveFlag(nameof(ShouldSerializeOwnerAbandonTime))] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _ownerAbandonTime; private bool ShouldSerializeOwnerAbandonTime() => _ownerAbandonTime != DateTime.MinValue; - [SerializableField(48)] + [SerializableField(47)] [SaveFlag(nameof(ShouldSerializeHasGeneratedLoot))] private bool _hasGeneratedLoot; private bool ShouldSerializeHasGeneratedLoot() => _hasGeneratedLoot; - // Field 49: IsParagon (hand-written property; the setter converts, which must not run at load) + // Field 48: IsParagon (hand-written property; the setter converts, which must not run at load) private bool _isParagon; private bool ShouldSerializeIsParagon() => _isParagon; [Tidy] - [SerializableField(50, setter: "private")] + [SerializableField(49, setter: "private")] [SaveFlag(nameof(ShouldSerializeFriends))] private List _friends; @@ -784,7 +796,7 @@ namespace Server.Mobiles return _friends?.Count > 0; } - [SerializableField(51)] + [SerializableField(50)] [SaveFlag(nameof(ShouldSerializeRemoveIfUntamed))] [SerializedCommandProperty(AccessLevel.GameMaster)] private bool _removeIfUntamed; @@ -792,14 +804,14 @@ namespace Server.Mobiles private bool ShouldSerializeRemoveIfUntamed() => _removeIfUntamed; [EncodedInt] - [SerializableField(52)] + [SerializableField(51)] [SaveFlag(nameof(ShouldSerializeRemoveStep))] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _removeStep; private bool ShouldSerializeRemoveStep() => _removeStep != 0; - [SerializableField(53, setter: "private")] + [SerializableField(52, setter: "private")] [SaveFlag(nameof(ShouldSerializePendingDeleteTimer))] [DeserializeTimer(nameof(DeserializePendingDeleteTimer))] private Timer _pendingDeleteTimer; @@ -814,7 +826,7 @@ namespace Server.Mobiles _pendingDeleteTimer.Start(); } - [SerializableField(54)] + [SerializableField(53)] [SaveFlag(nameof(ShouldSerializeCorpseNameOverride))] [SerializedCommandProperty(AccessLevel.GameMaster)] private string _corpseNameOverride; @@ -880,10 +892,11 @@ namespace Server.Mobiles if (_activeSpeed <= 0 || _passiveSpeed <= 0) { - // A 0-delay creature spins its AI timer at wheel resolution. - throw new InvalidOperationException( - $"{GetType()} constructed without speeds - is {"Data/npc-speeds.json"} missing?" - ); + // A 0-delay creature would spin its AI timer at wheel resolution. + logger.Debug("{Type} constructed without speeds - is Data/npc-speeds.json missing? Defaulting to Medium.", GetType()); + _activeSpeed = 0.25; + _passiveSpeed = 0.5; + _currentSpeed = _passiveSpeed; } _team = 0; @@ -972,7 +985,7 @@ namespace Server.Mobiles public virtual double WeaponAbilityChance => 0.4; - [SerializableProperty(49, useField: nameof(_isParagon))] + [SerializableProperty(48, useField: nameof(_isParagon))] [SaveFlag(nameof(ShouldSerializeIsParagon))] [CommandProperty(AccessLevel.GameMaster)] public bool IsParagon @@ -1183,8 +1196,6 @@ namespace Server.Mobiles } } - [SerializableProperty(16, useField: nameof(_controlMaster))] - [SaveFlag(nameof(ShouldSerializeControlMaster))] [CommandProperty(AccessLevel.GameMaster)] public Mobile ControlMaster { @@ -1209,8 +1220,6 @@ namespace Server.Mobiles } } - [SerializableProperty(24, useField: nameof(_summonMaster))] - [SaveFlag(nameof(ShouldSerializeSummonMaster))] [CommandProperty(AccessLevel.GameMaster)] public Mobile SummonMaster { @@ -1233,7 +1242,7 @@ namespace Server.Mobiles // Re-issuing the current order must still run the order logic (pet commands), so // this keeps a hand-written setter with no equality skip. - [SerializableProperty(19, useField: nameof(_controlOrder))] + [SerializableProperty(18, useField: nameof(_controlOrder))] [SaveFlag(nameof(ShouldSerializeControlOrder))] [CommandProperty(AccessLevel.GameMaster)] public OrderType ControlOrder @@ -1268,7 +1277,7 @@ namespace Server.Mobiles [CommandProperty(AccessLevel.GameMaster)] public DateTime BardEndTime { get; set; } - [SerializableProperty(21, useField: nameof(_tamable))] + [SerializableProperty(20, useField: nameof(_tamable))] [SaveFlag(nameof(ShouldSerializeTamable))] [CommandProperty(AccessLevel.GameMaster)] public bool Tamable @@ -2368,6 +2377,10 @@ namespace Server.Mobiles _speedEntry = null; } + // Feed the masters through the consolidated reference so the + // AfterDeserialization fan-out is uniform across both load paths. + _master = _controlMaster ?? _summonMaster; + if (version <= 14 && _isParagon && Hue == 0x31) { Hue = Paragon.Hue; // Paragon hue fixed, should now be 0x501. @@ -2377,6 +2390,9 @@ namespace Server.Mobiles [AfterDeserialization] private void AfterDeserialization() { + _controlMaster = _controlled ? _master : null; + _summonMaster = _summoned ? _master : null; + if (Core.AOS && NameHue == 0x35) { NameHue = -1; @@ -3622,8 +3638,8 @@ namespace Server.Mobiles var m = _controlMaster; SetControlMaster(null); + SummonMaster = null; // uncontrolled summons have no control master to clear through - SummonMaster = null; ReceivedHonorContext?.Cancel(); base.OnDelete(); @@ -3669,6 +3685,11 @@ namespace Server.Mobiles Controlled = false; ControlTarget = null; ControlOrder = OrderType.None; + + if (_summoned) + { + SummonMaster = null; + } } else { @@ -3693,6 +3714,11 @@ namespace Server.Mobiles ControlTarget = null; ControlOrder = OrderType.Come; + if (_summoned) + { + SummonMaster = m; + } + if (_pendingDeleteTimer != null) { diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 83c0e4cca..d60a10aa7 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -3553,7 +3553,6 @@ namespace Server.Mobiles pet.Internalize(); pet.SetControlMaster(null); - pet.SummonMaster = null; pet.IsStabled = true; pet.StabledBy = this; @@ -3601,12 +3600,6 @@ namespace Server.Mobiles if (Followers + pet.ControlSlots <= FollowersMax) { pet.SetControlMaster(this); - - if (pet.Summoned) - { - pet.SummonMaster = this; - } - pet.ControlTarget = this; pet.ControlOrder = OrderType.Follow; diff --git a/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs b/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs index 847209869..0fb347fd4 100644 --- a/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs +++ b/Projects/UOContent/Mobiles/Vendors/NPC/AnimalTrainer.cs @@ -255,7 +255,6 @@ namespace Server.Mobiles pet.Internalize(); pet.SetControlMaster(null); - pet.SummonMaster = null; pet.IsStabled = true; pet.StabledBy = from; @@ -356,12 +355,6 @@ namespace Server.Mobiles private void DoClaim(Mobile from, BaseCreature pet) { pet.SetControlMaster(from); - - if (pet.Summoned) - { - pet.SummonMaster = from; - } - pet.ControlTarget = from; pet.ControlOrder = OrderType.Follow;