From 08dab47413b566252837b0e388a4f7789e5c657e Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:45:48 -0700 Subject: [PATCH] fix: check Controlled before reading ControlMaster (#2668) Readers of `ControlMaster` that act on "this creature is a player's pet" now also require `Controlled`. Nothing sets a master on an uncontrolled creature today, so behavior does not change, except that the ML notoriety branch was the one read with nothing in front of it. The guard keeps these readers correct if a creature ever keeps a master without being controlled (a suspended-control state such as a charm or turn-pet ability, or folding SummonMaster into one master field). - Notoriety (ML): a creature only takes its master's notoriety while controlled - BaseCreature.AggressiveAction: the aggressor link to the master only applies to controlled pets - PlayerMobile.AutoStablePets: uncontrolled summons (blade spirits, energy vortexes) are skipped - CrystalCaveBarrier: only a controlled pet passes on its owner's quest progress - Discordance: the own-pet exception only applies to a controlled pet - Dismount (ML): the remount block only goes to the owner of a controlled pet Also removes the "Summons from monsters can attack players" branch in Mobile_AllowHarmful. It has been unreachable since #2000: a creature with no player master returns from the NPC check above it. Tests: UOContent.Tests 1158 passed. --- .../Engines/Quests/Dark Tides/Items/CrystalCaveBarrier.cs | 2 +- Projects/UOContent/Items/Weapons/Abilities/Dismount.cs | 2 +- Projects/UOContent/Misc/Notoriety.cs | 7 +------ Projects/UOContent/Mobiles/BaseCreature.cs | 2 +- Projects/UOContent/Mobiles/PlayerMobile.cs | 2 +- Projects/UOContent/Skills/Discordance.cs | 2 +- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Projects/UOContent/Engines/Quests/Dark Tides/Items/CrystalCaveBarrier.cs b/Projects/UOContent/Engines/Quests/Dark Tides/Items/CrystalCaveBarrier.cs index d3f7d4ef5..170cbcf91 100644 --- a/Projects/UOContent/Engines/Quests/Dark Tides/Items/CrystalCaveBarrier.cs +++ b/Projects/UOContent/Engines/Quests/Dark Tides/Items/CrystalCaveBarrier.cs @@ -20,7 +20,7 @@ public partial class CrystalCaveBarrier : Item if (m is BaseCreature creature) { - mob = creature.ControlMaster; + mob = creature.Controlled ? creature.ControlMaster : null; } if (mob is not PlayerMobile pm) diff --git a/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs b/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs index 3121b9516..df50f1449 100644 --- a/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs +++ b/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs @@ -102,7 +102,7 @@ public class Dismount : WeaponAbility { playerMobile.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, true); } - else if (Core.ML && attacker is BaseCreature { ControlMaster: PlayerMobile pm }) + else if (Core.ML && attacker is BaseCreature { Controlled: true, ControlMaster: PlayerMobile pm }) { pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false); } diff --git a/Projects/UOContent/Misc/Notoriety.cs b/Projects/UOContent/Misc/Notoriety.cs index 4416b09b5..27f54c18f 100644 --- a/Projects/UOContent/Misc/Notoriety.cs +++ b/Projects/UOContent/Misc/Notoriety.cs @@ -233,11 +233,6 @@ namespace Server.Misc return false; // Cannot harm other controlled mobiles from players } - if (pmFrom == null && bcFrom != null && bcFrom.Summoned && target.Player) - { - return true; // Summons from monsters can attack players - } - if (target.Player) { return false; // Cannot harm other players @@ -422,7 +417,7 @@ namespace Server.Misc return Notoriety.CanBeAttacked; } - master = bcTarg.ControlMaster; + master = bcTarg.Controlled ? bcTarg.ControlMaster : null; if (Core.ML && master != null) { diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 6d8c22d0d..18743579d 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -2734,7 +2734,7 @@ namespace Server.Mobiles { base.AggressiveAction(aggressor, criminal); - if (ControlMaster != null && NotorietyHandlers.CheckAggressor(ControlMaster.Aggressors, aggressor)) + if (Controlled && ControlMaster != null && NotorietyHandlers.CheckAggressor(ControlMaster.Aggressors, aggressor)) { aggressor.Aggressors.Add(AggressorInfo.Create(this, aggressor, true)); } diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 39be0e10a..cf7fc000b 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -3517,7 +3517,7 @@ namespace Server.Mobiles foreach (var follower in allFollowers) { - if (follower is not BaseCreature pet || pet.ControlMaster == null) + if (follower is not BaseCreature { Controlled: true } pet || pet.ControlMaster == null) { continue; } diff --git a/Projects/UOContent/Skills/Discordance.cs b/Projects/UOContent/Skills/Discordance.cs index 15b7ed118..14c5a7ee7 100644 --- a/Projects/UOContent/Skills/Discordance.cs +++ b/Projects/UOContent/Skills/Discordance.cs @@ -141,7 +141,7 @@ namespace Server.SkillHandlers { if (targ == from || targ is BaseCreature bc && (bc.BardImmune || !from.CanBeHarmful(bc, false)) && - bc.ControlMaster != from) + !(bc.Controlled && bc.ControlMaster == from)) { from.SendLocalizedMessage(1049535); // A song of discord would have no effect on that. }