refactor: master views over BaseCreature's one master reference (#2674)
## Summary After #2670, `BaseCreature` stores one master. Callers still reached it through three differently gated names plus hand-rolled combinations of the `Controlled`/`Summoned` flags. Each gate is now one named view: | Member | Definition | Meaning | |---|---|---| | `Master` | the reference; its setter does the follower bookkeeping; serialized as before | whoever the creature answers to | | `ControlMaster` | `Controlled ? Master : null` | the owner | | `SummonMaster` | `Summoned ? Master : null` | the summoner | | `GetMaster()` | `ControlMaster ?? SummonMaster` | who answers for the creature | `ControlMaster` and `SummonMaster` stay settable so custom code keeps compiling: both setters assign `Master`. There is no guard, so a write through either one can read back as null until the matching flag is set. An enraged creature is the one shape with a master and neither flag. It keeps its meer through `Master`, while `GetMaster()` stays null, so the meer never answers for it (notoriety, kill credit). Readers move onto the view they mean: - "Owner or summoner" pairs and `ControlMaster ?? SummonMaster` collapse into `GetMaster()`: pack instinct, familiars, golem, house access, mounts, pack horse, Solen friendship, spell target and guild checks, and the `[` pet command. - Checks such as `Controlled && ControlMaster == from` drop the flag the view already applies. Checks that don't reduce keep it: `Controlled && ControlMaster != from`, a possibly-null `from`, and `MageAI.CanDispel`'s `Summoned &&`. **No behavior change, save format unchanged** (the schema generator produces no diff). Edge cases for custom code and shard operators: - `SummonMaster = x` on a plain pet now replaces its owner (#2670 made that a no-op), and `ControlMaster = null` on an uncontrolled summon now clears its caster (it did nothing before). - `SpellHelper.GetGuildFor` no longer falls back to an enraged creature's meer. Meer mages have no guild. ## Docs - `dev-docs/content-patterns.md` § Masters: which view answers which question, how to set a master, and which flag checks are redundant. - `dev-docs/runuo-migration-docs/09-items-mobiles-creatures.md` § Masters: RunUO's two-field reads mapped onto the views, plus the gotcha for creatures that set `SummonMaster` without `Summoned` (the write compiles, the read now returns null; use `Master`). - The `modernuo-content-patterns` and `migrate-items-mobiles` skills point at both. ## Test plan - [x] `MasterViewsTests`: all four views across wild, pet, controlled summon, uncontrolled summon and enraged shapes; reassigning `Master` moves follower slots; `SetControlMaster(null)` keeps an uncontrolled summon's caster; deleting a summon returns the caster's slots - [x] Serialization tests updated to the new views (round-trip, legacy v22, v23 migration) - [x] UOContent.Tests (1178 passed, 2 skipped) and Server.Tests (907) green
This commit is contained in:
parent
37970a4811
commit
843a29e7dc
41 changed files with 346 additions and 213 deletions
|
|
@ -213,7 +213,7 @@ namespace Server.Commands
|
|||
{
|
||||
if (m is BaseCreature bc)
|
||||
{
|
||||
if (bc.Controlled && bc.ControlMaster == master || bc.Summoned && bc.SummonMaster == master)
|
||||
if (bc.GetMaster() == master)
|
||||
{
|
||||
pets.Add(bc);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -637,7 +637,7 @@ namespace Server.Engines.ConPVP
|
|||
|
||||
foreach (var mob in facet.GetMobilesInBounds(m_Bounds))
|
||||
{
|
||||
if (mob is BaseCreature pet && pet.Controlled && pet.ControlMaster != null &&
|
||||
if (mob is BaseCreature pet && pet.ControlMaster != null &&
|
||||
Players.Contains(pet.ControlMaster))
|
||||
{
|
||||
pets.Add(pet);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Server.Engines.ConPVP
|
|||
}
|
||||
|
||||
var pm = m as PlayerMobile ??
|
||||
(m is BaseCreature bc && bc.Summoned ? bc.SummonMaster as PlayerMobile : null);
|
||||
(m as BaseCreature)?.SummonMaster as PlayerMobile;
|
||||
|
||||
if (pm?.DuelContext?.StartedBeginCountdown == true)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ namespace Server.Engines.Doom
|
|||
return;
|
||||
}
|
||||
}
|
||||
else if (m is BaseCreature bc && (bc.Controlled && bc.ControlMaster == m_Controller.Successful ||
|
||||
else if (m is BaseCreature bc && (bc.ControlMaster == m_Controller.Successful ||
|
||||
bc.Summoned))
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class ConfirmReleaseGump : StaticGump<ConfirmReleaseGump>
|
|||
public override void OnResponse(NetState sender, in RelayInfo info)
|
||||
{
|
||||
if (info.ButtonID != 2 || _pet.Deleted ||
|
||||
!(_pet.Controlled && _from == _pet.ControlMaster &&
|
||||
!(_from == _pet.ControlMaster &&
|
||||
_from.CheckAlive() && _pet.Map == _from.Map && _pet.InRange(_from, 14)))
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ public partial class StrongBackpack : Backpack // Used on Pack animals
|
|||
base.CheckHold(m, item, false, checkItems, plusItems, plusWeight);
|
||||
|
||||
public override bool CheckContentDisplay(Mobile from) =>
|
||||
RootParent is BaseCreature creature && creature.Controlled && creature.ControlMaster == from ||
|
||||
RootParent is BaseCreature creature && creature.ControlMaster == from ||
|
||||
base.CheckContentDisplay(from);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ public class Dismount : WeaponAbility
|
|||
{
|
||||
playerMobile.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, true);
|
||||
}
|
||||
else if (Core.ML && attacker is BaseCreature { Controlled: true, ControlMaster: PlayerMobile pm })
|
||||
else if (Core.ML && attacker is BaseCreature { ControlMaster: PlayerMobile pm })
|
||||
{
|
||||
pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1698,12 +1698,12 @@ public abstract partial class BaseWeapon
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (attacker is not BaseCreature bc || bc.PackInstinct == PackInstinct.None || !bc.Controlled && !bc.Summoned)
|
||||
if (attacker is not BaseCreature bc || bc.PackInstinct == PackInstinct.None)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var master = bc.ControlMaster ?? bc.SummonMaster;
|
||||
var master = bc.GetMaster();
|
||||
|
||||
if (master == null)
|
||||
{
|
||||
|
|
@ -1713,8 +1713,7 @@ public abstract partial class BaseWeapon
|
|||
var inPack = 1;
|
||||
foreach (var m in defender.GetMobilesInRange<BaseCreature>(1))
|
||||
{
|
||||
if (m != attacker && (m.PackInstinct & bc.PackInstinct) != 0 && (m.Controlled || m.Summoned) &&
|
||||
master == (m.ControlMaster ?? m.SummonMaster) && m.Combatant == defender)
|
||||
if (m != attacker && (m.PackInstinct & bc.PackInstinct) != 0 && master == m.GetMaster() && m.Combatant == defender)
|
||||
{
|
||||
inPack++;
|
||||
}
|
||||
|
|
@ -1736,7 +1735,7 @@ public abstract partial class BaseWeapon
|
|||
{
|
||||
foreach (var m in defender.GetMobilesInRange<Clone>(4))
|
||||
{
|
||||
if (m?.Summoned == true && m.SummonMaster == defender)
|
||||
if (m?.SummonMaster == defender)
|
||||
{
|
||||
// Your attack has been diverted to a nearby mirror image of your target!
|
||||
attacker.SendLocalizedMessage(1063141);
|
||||
|
|
@ -2100,7 +2099,7 @@ public abstract partial class BaseWeapon
|
|||
|
||||
if (attacker is VampireBatFamiliar bc)
|
||||
{
|
||||
var caster = bc.ControlMaster ?? bc.SummonMaster;
|
||||
var caster = bc.GetMaster();
|
||||
|
||||
if (caster != null && caster.Map == bc.Map)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ namespace Server.Misc
|
|||
}
|
||||
|
||||
if (bcTarg?.Controlled == true
|
||||
|| bcTarg?.Summoned == true && bcTarg.SummonMaster != from && bcTarg.SummonMaster?.Player == true)
|
||||
|| bcTarg?.SummonMaster is { Player: true } summoner && summoner != from)
|
||||
{
|
||||
return false; // Cannot harm other controlled mobiles from players
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public abstract partial class BaseAI
|
|||
}
|
||||
|
||||
// Only a creature somebody can command has been told anything; wild creatures rest on None.
|
||||
var toldToStandDown = Mobile.Controlled && Mobile.ControlMaster != null && Mobile.Commandable &&
|
||||
var toldToStandDown = Mobile.ControlMaster != null && Mobile.Commandable &&
|
||||
IsStandDownOrder(Mobile.ControlOrder);
|
||||
|
||||
if (Mobile.StandsDownOnCommand && toldToStandDown)
|
||||
|
|
@ -1049,15 +1049,15 @@ public abstract partial class BaseAI
|
|||
private bool IsInvalidSummonTarget(Mobile m, BaseCreature bc)
|
||||
{
|
||||
// A summon whose caster was deleted before a reload comes back with no master.
|
||||
if (Core.AOS && bc?.Summoned == true &&
|
||||
(bc.SummonMaster == Mobile || bc.SummonMaster is { Player: false } summonMaster && IsHostile(summonMaster)))
|
||||
if (Core.AOS &&
|
||||
(bc?.SummonMaster == Mobile || bc?.SummonMaster is { Player: false } summonMaster && IsHostile(summonMaster)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var master = Mobile.SummonMaster;
|
||||
|
||||
if (!Mobile.Summoned || master == null)
|
||||
if (master == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -775,7 +775,7 @@ public abstract partial class BaseAI
|
|||
var master = Mobile.ControlMaster;
|
||||
|
||||
return target == Mobile || master != null &&
|
||||
(target == master || target is BaseCreature { Controlled: true } creature && creature.ControlMaster == master);
|
||||
(target == master || target is BaseCreature creature && creature.ControlMaster == master);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ public class FamiliarAI : BaseAI
|
|||
var combatant = target.Combatant;
|
||||
|
||||
return combatant == Mobile || combatant == master ||
|
||||
combatant is BaseCreature { Controlled: true } pet && pet.ControlMaster == master;
|
||||
combatant is BaseCreature pet && pet.ControlMaster == master;
|
||||
}
|
||||
|
||||
// Closest mobile in a fight with the caster's side.
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public abstract class SummonUndeadCounter : MonsterAbility
|
|||
summon = CreateSummon(source);
|
||||
summon.Team = source.Team;
|
||||
summon.Summoned = true;
|
||||
summon.SummonMaster = source;
|
||||
summon.Master = source;
|
||||
summon.FightMode = FightMode.Closest;
|
||||
summon.Combatant = target;
|
||||
summon.Home = source.Home;
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ namespace Server.Mobiles
|
|||
public static bool CheckAccess(BaseCreature animal, Mobile from) =>
|
||||
from == animal || from.AccessLevel >= AccessLevel.GameMaster || from.Alive && animal.Controlled &&
|
||||
!animal.IsDeadPet &&
|
||||
(from == animal.ControlMaster || from == animal.SummonMaster || animal.IsPetFriend(from));
|
||||
(from == animal.GetMaster() || animal.IsPetFriend(from));
|
||||
|
||||
public static void CombineBackpacks(BaseCreature animal)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -239,8 +239,7 @@ public abstract partial class BaseMount : BaseCreature, IMount
|
|||
if (from.InRange(this, 1))
|
||||
{
|
||||
var canAccess = from.AccessLevel >= AccessLevel.GameMaster
|
||||
|| Controlled && ControlMaster == from
|
||||
|| Summoned && SummonMaster == from;
|
||||
|| GetMaster() == from;
|
||||
|
||||
if (canAccess)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ namespace Server.Mobiles
|
|||
|
||||
public override void OnHarmfulSpell(Mobile from)
|
||||
{
|
||||
if (!Controlled && ControlMaster == null)
|
||||
if (!Controlled)
|
||||
{
|
||||
CurrentSpeed = BoostedSpeed;
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ namespace Server.Mobiles
|
|||
|
||||
public override void OnCombatantChange()
|
||||
{
|
||||
if (Combatant == null && !Controlled && ControlMaster == null)
|
||||
if (Combatant == null && !Controlled)
|
||||
{
|
||||
CurrentSpeed = PassiveSpeed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,9 +445,7 @@ namespace Server.Mobiles
|
|||
InvalidateProperties();
|
||||
}
|
||||
|
||||
// ControlMaster and SummonMaster are views of this one reference, gated by Controlled and Summoned.
|
||||
[SerializableField(15, getter: "private", setter: "private")]
|
||||
[SaveFlag(nameof(ShouldSerializeMaster))]
|
||||
// Follower bookkeeping brackets the assignment, so Master is hand-written.
|
||||
private Mobile _master;
|
||||
|
||||
private bool ShouldSerializeMaster() => _master != null;
|
||||
|
|
@ -1152,51 +1150,49 @@ namespace Server.Mobiles
|
|||
}
|
||||
}
|
||||
|
||||
// Assigned before Controlled is raised (SetControlMaster), so the setter cannot be gated.
|
||||
// Whoever this creature answers to. ControlMaster, SummonMaster and GetMaster() gate it by
|
||||
// Controlled and Summoned; an enraged creature has a master with neither flag.
|
||||
[SerializableProperty(15, useField: nameof(_master))]
|
||||
[SaveFlag(nameof(ShouldSerializeMaster))]
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Mobile ControlMaster
|
||||
public Mobile Master
|
||||
{
|
||||
get => _controlled ? _master : null;
|
||||
get => _master;
|
||||
set
|
||||
{
|
||||
if (ControlMaster == value || this == value)
|
||||
if (_master == value || this == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetMaster(value);
|
||||
RemoveFollowers();
|
||||
_master = value;
|
||||
AddFollowers();
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
StopDeleteTimer();
|
||||
}
|
||||
|
||||
Delta(MobileDelta.Noto);
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// The summoner, controlled or not. Enraged creatures have one without being Summoned.
|
||||
// A plain pet has no summoner, so assigning one would overwrite its owner.
|
||||
/// <summary>The owner. Setting it sets <see cref="Master"/>.</summary>
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Mobile ControlMaster
|
||||
{
|
||||
get => _controlled ? _master : null;
|
||||
set => Master = value;
|
||||
}
|
||||
|
||||
/// <summary>The summoner, controlled or not. Setting it sets <see cref="Master"/>.</summary>
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Mobile SummonMaster
|
||||
{
|
||||
get => _controlled && !_summoned ? null : _master;
|
||||
set
|
||||
{
|
||||
if (SummonMaster == value || this == value || _controlled && !_summoned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetMaster(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMaster(Mobile value)
|
||||
{
|
||||
RemoveFollowers();
|
||||
Master = value;
|
||||
AddFollowers();
|
||||
|
||||
Delta(MobileDelta.Noto);
|
||||
get => _summoned ? _master : null;
|
||||
set => Master = value;
|
||||
}
|
||||
|
||||
// Fires on every assignment, not only changes: a reissued order is a command (retarget, re-anchor).
|
||||
|
|
@ -2726,7 +2722,7 @@ namespace Server.Mobiles
|
|||
AnimateDeadSpell.Unregister(SummonMaster, this);
|
||||
}
|
||||
|
||||
if (Summoned && SummonMaster != null)
|
||||
if (SummonMaster != null)
|
||||
{
|
||||
SummonFamiliarSpell.Unregister(SummonMaster, this);
|
||||
}
|
||||
|
|
@ -2796,7 +2792,7 @@ namespace Server.Mobiles
|
|||
{
|
||||
base.AggressiveAction(aggressor, criminal);
|
||||
|
||||
if (Controlled && ControlMaster != null && NotorietyHandlers.CheckAggressor(ControlMaster.Aggressors, aggressor))
|
||||
if (ControlMaster != null && NotorietyHandlers.CheckAggressor(ControlMaster.Aggressors, aggressor))
|
||||
{
|
||||
aggressor.Aggressors.Add(AggressorInfo.Create(this, aggressor, true));
|
||||
}
|
||||
|
|
@ -2904,7 +2900,7 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
public override bool IsHarmfulCriminal(Mobile target) =>
|
||||
target != ControlMaster && (!Summoned || target != SummonMaster) &&
|
||||
target != GetMaster() &&
|
||||
(target is not BaseCreature { InitialInnocent: true } creature || creature.Controlled) &&
|
||||
(target is not PlayerMobile mobile || mobile.PermaFlags.Count <= 0) && base.IsHarmfulCriminal(target);
|
||||
|
||||
|
|
@ -2912,9 +2908,9 @@ namespace Server.Mobiles
|
|||
{
|
||||
base.CriminalAction(message);
|
||||
|
||||
if ((Controlled || Summoned) && _master?.Player == true)
|
||||
if (GetMaster() is { Player: true } master)
|
||||
{
|
||||
_master.CriminalAction(false);
|
||||
master.CriminalAction(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2922,7 +2918,7 @@ namespace Server.Mobiles
|
|||
{
|
||||
base.DoHarmful(target, indirect);
|
||||
|
||||
if (target == this || target == _master || !Controlled && !Summoned)
|
||||
if (target == this || GetMaster() is not { } owner || target == owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -3343,20 +3339,8 @@ namespace Server.Mobiles
|
|||
return bonus;
|
||||
}
|
||||
|
||||
public Mobile GetMaster()
|
||||
{
|
||||
if (Controlled && ControlMaster != null)
|
||||
{
|
||||
return ControlMaster;
|
||||
}
|
||||
|
||||
if (Summoned && SummonMaster != null)
|
||||
{
|
||||
return SummonMaster;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
// Who answers for this creature: its owner or its summoner. Never an enraged creature's meer.
|
||||
public Mobile GetMaster() => ControlMaster ?? SummonMaster;
|
||||
|
||||
public virtual bool IsMonster => !Controlled || (GetMaster() as BaseCreature)?.IsMonster == true;
|
||||
|
||||
|
|
@ -3702,7 +3686,7 @@ namespace Server.Mobiles
|
|||
var m = ControlMaster;
|
||||
SetControlMaster(null);
|
||||
|
||||
SummonMaster = null;
|
||||
Master = null;
|
||||
ReceivedHonorContext?.Cancel();
|
||||
|
||||
base.OnDelete();
|
||||
|
|
@ -3757,14 +3741,19 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
public override bool CanBeRenamedBy(Mobile from) =>
|
||||
Controlled && from == ControlMaster && !from.Region.IsPartOf<JailRegion>() ||
|
||||
from == ControlMaster && !from.Region.IsPartOf<JailRegion>() ||
|
||||
base.CanBeRenamedBy(from);
|
||||
|
||||
public bool SetControlMaster(Mobile m)
|
||||
{
|
||||
if (m == null)
|
||||
{
|
||||
ControlMaster = null;
|
||||
// An uncontrolled summon keeps its caster.
|
||||
if (_controlled)
|
||||
{
|
||||
Master = null;
|
||||
}
|
||||
|
||||
Controlled = false;
|
||||
ControlTarget = null;
|
||||
ControlOrder = OrderType.None;
|
||||
|
|
@ -3787,7 +3776,7 @@ namespace Server.Mobiles
|
|||
|
||||
Home = Point3D.Zero;
|
||||
|
||||
ControlMaster = m;
|
||||
Master = m;
|
||||
Controlled = true;
|
||||
ControlTarget = null;
|
||||
ControlOrder = OrderType.Come;
|
||||
|
|
@ -3849,7 +3838,7 @@ namespace Server.Mobiles
|
|||
|
||||
creature.RangeHome = 10;
|
||||
creature.Summoned = true;
|
||||
creature.SummonMaster = caster;
|
||||
creature.Master = caster;
|
||||
|
||||
var pack = creature.Backpack;
|
||||
|
||||
|
|
@ -4006,9 +3995,11 @@ namespace Server.Mobiles
|
|||
return BardMaster;
|
||||
}
|
||||
|
||||
if ((_controlled || _summoned) && _master != null)
|
||||
var master = GetMaster();
|
||||
|
||||
if (master != null)
|
||||
{
|
||||
return _master;
|
||||
return master;
|
||||
}
|
||||
|
||||
return base.GetDamageMaster(damagee);
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public abstract partial class BaseFamiliar : BaseCreature
|
|||
{
|
||||
base.GetContextMenuEntries(from, ref list);
|
||||
|
||||
if (from.Alive && Controlled && from == ControlMaster && from.InRange(this, 14))
|
||||
if (from.Alive && from == ControlMaster && from.InRange(this, 14))
|
||||
{
|
||||
list.Add(new ReleaseEntry());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public partial class DarkWolfFamiliar : BaseFamiliar
|
|||
|
||||
m_NextRestore = Core.Now + TimeSpan.FromSeconds(2.0);
|
||||
|
||||
var caster = ControlMaster ?? SummonMaster;
|
||||
var caster = GetMaster();
|
||||
|
||||
if (caster != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public partial class ShadowWispFamiliar : BaseFamiliar
|
|||
|
||||
private void Flare()
|
||||
{
|
||||
var caster = ControlMaster ?? SummonMaster;
|
||||
var caster = GetMaster();
|
||||
|
||||
if (caster == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,14 +21,9 @@ namespace Server.Mobiles
|
|||
{
|
||||
if (m is BaseCreature bc)
|
||||
{
|
||||
if (bc.Controlled && bc.ControlMaster is PlayerMobile)
|
||||
if (bc.GetMaster() is PlayerMobile master)
|
||||
{
|
||||
return CheckRedFriendship(bc.ControlMaster);
|
||||
}
|
||||
|
||||
if (bc.Summoned && bc.SummonMaster is PlayerMobile)
|
||||
{
|
||||
return CheckRedFriendship(bc.SummonMaster);
|
||||
return CheckRedFriendship(master);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,14 +34,9 @@ namespace Server.Mobiles
|
|||
{
|
||||
if (m is BaseCreature bc)
|
||||
{
|
||||
if (bc.Controlled && bc.ControlMaster is PlayerMobile)
|
||||
if (bc.GetMaster() is PlayerMobile master)
|
||||
{
|
||||
return CheckBlackFriendship(bc.ControlMaster);
|
||||
}
|
||||
|
||||
if (bc.Summoned && bc.SummonMaster is PlayerMobile)
|
||||
{
|
||||
return CheckBlackFriendship(bc.SummonMaster);
|
||||
return CheckBlackFriendship(master);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,13 +47,9 @@ namespace Server.Mobiles
|
|||
{
|
||||
if (from is BaseCreature bc)
|
||||
{
|
||||
if (bc.Controlled && bc.ControlMaster is PlayerMobile)
|
||||
if (bc.GetMaster() is PlayerMobile master)
|
||||
{
|
||||
OnRedDamage(bc.ControlMaster);
|
||||
}
|
||||
else if (bc.Summoned && bc.SummonMaster is PlayerMobile)
|
||||
{
|
||||
OnRedDamage(bc.SummonMaster);
|
||||
OnRedDamage(master);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,13 +69,9 @@ namespace Server.Mobiles
|
|||
{
|
||||
if (from is BaseCreature bc)
|
||||
{
|
||||
if (bc.Controlled && bc.ControlMaster is PlayerMobile)
|
||||
if (bc.GetMaster() is PlayerMobile master)
|
||||
{
|
||||
OnBlackDamage(bc.ControlMaster);
|
||||
}
|
||||
else if (bc.Summoned && bc.SummonMaster is PlayerMobile)
|
||||
{
|
||||
OnBlackDamage(bc.SummonMaster);
|
||||
OnBlackDamage(master);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,15 +92,15 @@ namespace Server.Mobiles
|
|||
Karma = -1000;
|
||||
Tamable = false;
|
||||
|
||||
SummonMaster = summoner;
|
||||
Master = summoner;
|
||||
}
|
||||
|
||||
// The meer's FightMode.Evil against our negative karma would otherwise make us enemies.
|
||||
public override bool IsEnemy(Mobile m) => m != SummonMaster && base.IsEnemy(m);
|
||||
public override bool IsEnemy(Mobile m) => m != Master && base.IsEnemy(m);
|
||||
|
||||
public override void OnThink()
|
||||
{
|
||||
if (SummonMaster?.Deleted != false)
|
||||
if (Master?.Deleted != false)
|
||||
{
|
||||
Delete();
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ namespace Server.Mobiles
|
|||
*/
|
||||
else if (!Combat(this))
|
||||
{
|
||||
AIObject?.MoveTo(SummonMaster, 5);
|
||||
AIObject?.MoveTo(Master, 5);
|
||||
}
|
||||
/*
|
||||
On OSI, if the summon attacks a mobile, the summoner meer also
|
||||
|
|
@ -119,11 +119,11 @@ namespace Server.Mobiles
|
|||
is a player or controlled/summoned, and the summoner is not already
|
||||
engaged in combat.
|
||||
*/
|
||||
else if (!Combat(SummonMaster))
|
||||
else if (!Combat(Master))
|
||||
{
|
||||
if (Combatant.Player || Combatant is BaseCreature bc && bc.GetMaster() != null)
|
||||
{
|
||||
SummonMaster.Combatant = Combatant;
|
||||
Master.Combatant = Combatant;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace Server.Mobiles
|
|||
public override bool InitialInnocent => true;
|
||||
|
||||
// FightMode.Evil would otherwise target our own enraged creatures for their negative karma.
|
||||
public override bool IsEnemy(Mobile m) => (m as BaseEnraged)?.SummonMaster != this && base.IsEnemy(m);
|
||||
public override bool IsEnemy(Mobile m) => (m as BaseEnraged)?.Master != this && base.IsEnemy(m);
|
||||
|
||||
public override void GenerateLoot()
|
||||
{
|
||||
|
|
@ -88,7 +88,7 @@ namespace Server.Mobiles
|
|||
|
||||
if (combatant is BaseCreature bc)
|
||||
{
|
||||
if (bc.Controlled && bc.ControlMaster?.Deleted == false && bc.ControlMaster.Alive)
|
||||
if (bc.ControlMaster is { Deleted: false, Alive: true })
|
||||
{
|
||||
if (bc.ControlMaster.Map == Map && bc.ControlMaster.InRange(this, 12) &&
|
||||
!UnderEffect(bc.ControlMaster))
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Server.Mobiles
|
|||
public override bool BleedImmune => true;
|
||||
public override Poison PoisonImmune => Poison.Lethal;
|
||||
|
||||
public override bool FollowsAcquireRules => Core.AOS || !Summoned || SummonMaster?.Player != true ||
|
||||
public override bool FollowsAcquireRules => Core.AOS || SummonMaster?.Player != true ||
|
||||
(Map?.Rules & MapRules.HarmfulRestrictions) != 0;
|
||||
|
||||
public override double GetFightModeRanking(Mobile m, FightMode acqType, bool bPlayerOnly) =>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ namespace Server.Mobiles
|
|||
public override bool BleedImmune => true;
|
||||
public override Poison PoisonImmune => Poison.Lethal;
|
||||
|
||||
public override bool FollowsAcquireRules => Core.AOS || !Summoned || SummonMaster?.Player != true ||
|
||||
public override bool FollowsAcquireRules => Core.AOS || SummonMaster?.Player != true ||
|
||||
(Map?.Rules & MapRules.HarmfulRestrictions) != 0;
|
||||
|
||||
public override double GetFightModeRanking(Mobile m, FightMode acqType, bool bPlayerOnly) =>
|
||||
|
|
|
|||
|
|
@ -142,22 +142,17 @@ namespace Server.Mobiles
|
|||
|
||||
public override void OnDamage(int amount, Mobile from, bool willKill)
|
||||
{
|
||||
if (Controlled || Summoned)
|
||||
if (GetMaster() is { Player: true } master && master.Map == Map && master.InRange(Location, 20))
|
||||
{
|
||||
var master = ControlMaster ?? SummonMaster;
|
||||
|
||||
if (master?.Player == true && master.Map == Map && master.InRange(Location, 20))
|
||||
if (master.Mana >= amount)
|
||||
{
|
||||
if (master.Mana >= amount)
|
||||
{
|
||||
master.Mana -= amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount -= master.Mana;
|
||||
master.Mana = 0;
|
||||
master.Damage(amount);
|
||||
}
|
||||
master.Mana -= amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount -= master.Mana;
|
||||
master.Mana = 0;
|
||||
master.Damage(amount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ namespace Server.Mobiles
|
|||
|
||||
public override void OnHarmfulSpell(Mobile from)
|
||||
{
|
||||
if (!Controlled && ControlMaster == null)
|
||||
if (!Controlled)
|
||||
{
|
||||
CurrentSpeed = BoostedSpeed;
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ namespace Server.Mobiles
|
|||
|
||||
public override void OnCombatantChange()
|
||||
{
|
||||
if (Combatant == null && !Controlled && ControlMaster == null)
|
||||
if (Combatant == null && !Controlled)
|
||||
{
|
||||
CurrentSpeed = PassiveSpeed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3660,12 +3660,7 @@ namespace Server.Multis
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!(bc.Controlled || bc.Summoned))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m = bc.ControlMaster ?? bc.SummonMaster;
|
||||
m = bc.GetMaster();
|
||||
|
||||
return m != null && (m.AccessLevel > AccessLevel.Player || IsFriend(m) || Access?.Contains(m) == true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ namespace Server.SkillHandlers
|
|||
{
|
||||
if (targ == from || targ is BaseCreature bc &&
|
||||
(bc.BardImmune || !from.CanBeHarmful(bc, false)) &&
|
||||
!(bc.Controlled && bc.ControlMaster == from))
|
||||
bc.ControlMaster != from)
|
||||
{
|
||||
from.SendLocalizedMessage(1049535); // A song of discord would have no effect on that.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -407,32 +407,8 @@ namespace Server.Spells
|
|||
};
|
||||
}
|
||||
|
||||
public static Guild GetGuildFor(Mobile m)
|
||||
{
|
||||
var g = m.Guild as Guild;
|
||||
|
||||
if (g == null && m is BaseCreature c)
|
||||
{
|
||||
m = c.ControlMaster;
|
||||
|
||||
if (m != null)
|
||||
{
|
||||
g = m.Guild as Guild;
|
||||
}
|
||||
|
||||
if (g == null)
|
||||
{
|
||||
m = c.SummonMaster;
|
||||
|
||||
if (m != null)
|
||||
{
|
||||
g = m.Guild as Guild;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
public static Guild GetGuildFor(Mobile m) =>
|
||||
m.Guild as Guild ?? (m as BaseCreature)?.GetMaster()?.Guild as Guild;
|
||||
|
||||
public static bool ValidIndirectTarget(Mobile from, Mobile to)
|
||||
{
|
||||
|
|
@ -473,29 +449,17 @@ namespace Server.Spells
|
|||
return false;
|
||||
}
|
||||
|
||||
if (bcTarg != null && (bcTarg.Controlled || bcTarg.Summoned))
|
||||
if (bcTarg?.GetMaster() is { } targMaster)
|
||||
{
|
||||
if (bcTarg.ControlMaster == from || bcTarg.SummonMaster == from)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p != null && (p.Contains(bcTarg.ControlMaster) || p.Contains(bcTarg.SummonMaster)))
|
||||
if (targMaster == from || p?.Contains(targMaster) == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bcFrom != null && (bcFrom.Controlled || bcFrom.Summoned))
|
||||
if (bcFrom?.GetMaster() is { } fromMaster)
|
||||
{
|
||||
if (bcFrom.ControlMaster == to || bcFrom.SummonMaster == to)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p = Party.Get(to);
|
||||
|
||||
if (p != null && (p.Contains(bcFrom.ControlMaster) || p.Contains(bcFrom.SummonMaster)))
|
||||
if (fromMaster == to || Party.Get(to)?.Contains(fromMaster) == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ namespace Server.Mobiles
|
|||
Warmode = true;
|
||||
|
||||
Summoned = true;
|
||||
SummonMaster = caster;
|
||||
Master = caster;
|
||||
|
||||
ControlOrder = OrderType.Follow;
|
||||
ControlTarget = caster;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue