fix: Fixes stabled, abilities targeting self, and unsummon memory leak (#1418)
## **MAJOR CHANGE** * `Stabled` has been moved to `PlayerMobile`. * New methods added, `PlayerMobile.AddStabled` and `PlayerMobile.RemoveStabled`. * Added `PlayerMobile.AddFollower` and `PlayerMobile.RemoveFollower`. * `Stabled`, `AutoStabled`, and `AllFollowers` are now `HashSet` and **_CAN BE NULL_**. ### Summary * Fixes monster abilities causing harm to the monster through reflect * Adds `CanTriggerAgainstSelf` to override this for healing or some other self-affecting ability * Fixes a major memory leak where `UnsummonTimer` from animated dead spell lasts up-to 24hrs and therefore holds onto references of dead/deleted mobs. * Fixes another minor leak where a mob is not unregistered from the animated dead spell list until the next spell cast.
This commit is contained in:
parent
d99e72db8f
commit
3b97e8d39c
13 changed files with 391 additions and 280 deletions
18
Projects/Server/Mobiles/Mobile.Migrations.cs
Normal file
18
Projects/Server/Mobiles/Mobile.Migrations.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Server;
|
||||||
|
|
||||||
|
public partial class Mobile
|
||||||
|
{
|
||||||
|
// Migrating Stabled property to PlayerMobile
|
||||||
|
public static Dictionary<Mobile, HashSet<Mobile>> StableMigrations { get; private set; }
|
||||||
|
|
||||||
|
public static void AddToStabledMigration(Mobile m, HashSet<Mobile> stabled)
|
||||||
|
{
|
||||||
|
if (stabled?.Count > 0)
|
||||||
|
{
|
||||||
|
StableMigrations ??= new Dictionary<Mobile, HashSet<Mobile>>();
|
||||||
|
StableMigrations[m] = stabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -180,7 +180,7 @@ public delegate int AOSStatusHandler(Mobile from, int index);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class representing players, npcs, and creatures.
|
/// Base class representing players, npcs, and creatures.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyListEntity
|
public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyListEntity
|
||||||
{
|
{
|
||||||
// Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds
|
// Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds
|
||||||
private const int WarmodeCatchCount = 4;
|
private const int WarmodeCatchCount = 4;
|
||||||
|
|
@ -426,8 +426,6 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Mobile> Stabled { get; private set; }
|
|
||||||
|
|
||||||
[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
|
[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
|
||||||
public VirtueInfo Virtues { get; private set; }
|
public VirtueInfo Virtues { get; private set; }
|
||||||
|
|
||||||
|
|
@ -2279,7 +2277,7 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
|
|
||||||
public virtual void Serialize(IGenericWriter writer)
|
public virtual void Serialize(IGenericWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(33); // version
|
writer.Write(34); // version
|
||||||
|
|
||||||
writer.WriteDeltaTime(LastStrGain);
|
writer.WriteDeltaTime(LastStrGain);
|
||||||
writer.WriteDeltaTime(LastIntGain);
|
writer.WriteDeltaTime(LastIntGain);
|
||||||
|
|
@ -2317,8 +2315,15 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
|
|
||||||
// writer.Write(CreationTime);
|
// writer.Write(CreationTime);
|
||||||
|
|
||||||
Stabled.Tidy();
|
// if (Stabled == null)
|
||||||
writer.Write(Stabled);
|
// {
|
||||||
|
// writer.Write(0);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// Stabled.Tidy();
|
||||||
|
// writer.Write(Stabled);
|
||||||
|
// }
|
||||||
|
|
||||||
writer.Write(CantWalk);
|
writer.Write(CantWalk);
|
||||||
|
|
||||||
|
|
@ -2448,11 +2453,6 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < Stabled.Count; i++)
|
|
||||||
{
|
|
||||||
Stabled[i].Delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
SendRemovePacket();
|
SendRemovePacket();
|
||||||
|
|
||||||
m_Guild?.OnDelete(this);
|
m_Guild?.OnDelete(this);
|
||||||
|
|
@ -6074,6 +6074,11 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
|
|
||||||
switch (version)
|
switch (version)
|
||||||
{
|
{
|
||||||
|
case 34:
|
||||||
|
{
|
||||||
|
// Moved Stabled to PlayerMobile
|
||||||
|
goto case 33;
|
||||||
|
}
|
||||||
case 33:
|
case 33:
|
||||||
{
|
{
|
||||||
// Removed created
|
// Removed created
|
||||||
|
|
@ -6148,7 +6153,11 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
case 22: // Just removed followers
|
case 22: // Just removed followers
|
||||||
case 21:
|
case 21:
|
||||||
{
|
{
|
||||||
Stabled = reader.ReadEntityList<Mobile>();
|
if (version < 34)
|
||||||
|
{
|
||||||
|
// Migrated to PlayerMobile
|
||||||
|
AddToStabledMigration(this, reader.ReadEntitySet<Mobile>(true));
|
||||||
|
}
|
||||||
|
|
||||||
goto case 20;
|
goto case 20;
|
||||||
}
|
}
|
||||||
|
|
@ -6285,11 +6294,6 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
}
|
}
|
||||||
case 0:
|
case 0:
|
||||||
{
|
{
|
||||||
if (version < 21)
|
|
||||||
{
|
|
||||||
Stabled = new List<Mobile>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version < 18)
|
if (version < 18)
|
||||||
{
|
{
|
||||||
Virtues = new VirtueInfo();
|
Virtues = new VirtueInfo();
|
||||||
|
|
@ -7721,7 +7725,6 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
||||||
Aggressors = new List<AggressorInfo>();
|
Aggressors = new List<AggressorInfo>();
|
||||||
Aggressed = new List<AggressorInfo>();
|
Aggressed = new List<AggressorInfo>();
|
||||||
Virtues = new VirtueInfo();
|
Virtues = new VirtueInfo();
|
||||||
Stabled = new List<Mobile>();
|
|
||||||
DamageEntries = new List<DamageEntry>();
|
DamageEntries = new List<DamageEntry>();
|
||||||
|
|
||||||
NextSkillTime = Core.TickCount;
|
NextSkillTime = Core.TickCount;
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,18 @@ public static class SerializationExtensions
|
||||||
return entity?.Created <= reader.LastSerialized ? entity : null;
|
return entity?.Created <= reader.LastSerialized ? entity : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<T> ReadEntityList<T>(this IGenericReader reader) where T : class, ISerializable
|
public static List<T> ReadEntityList<T>(
|
||||||
|
this IGenericReader reader,
|
||||||
|
bool nullIfEmpty = false
|
||||||
|
) where T : class, ISerializable
|
||||||
{
|
{
|
||||||
var count = reader.ReadInt();
|
var count = reader.ReadInt();
|
||||||
|
|
||||||
|
if (count == 0 && nullIfEmpty)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var list = new List<T>(count);
|
var list = new List<T>(count);
|
||||||
|
|
||||||
for (var i = 0; i < count; ++i)
|
for (var i = 0; i < count; ++i)
|
||||||
|
|
@ -66,10 +74,18 @@ public static class SerializationExtensions
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HashSet<T> ReadEntitySet<T>(this IGenericReader reader) where T : class, ISerializable
|
public static HashSet<T> ReadEntitySet<T>(
|
||||||
|
this IGenericReader reader,
|
||||||
|
bool nullIfEmpty = false
|
||||||
|
) where T : class, ISerializable
|
||||||
{
|
{
|
||||||
var count = reader.ReadInt();
|
var count = reader.ReadInt();
|
||||||
|
|
||||||
|
if (count == 0 && nullIfEmpty)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var set = new HashSet<T>(count);
|
var set = new HashSet<T>(count);
|
||||||
|
|
||||||
for (var i = 0; i < count; ++i)
|
for (var i = 0; i < count; ++i)
|
||||||
|
|
|
||||||
|
|
@ -287,37 +287,34 @@ namespace Server.Commands
|
||||||
{
|
{
|
||||||
var pets = pm.AllFollowers;
|
var pets = pm.AllFollowers;
|
||||||
|
|
||||||
if (pets.Count > 0)
|
if (!(pets?.Count > 0))
|
||||||
{
|
{
|
||||||
CommandLogging.WriteLine(
|
from.SendMessage("There were no pets found for that player.");
|
||||||
from,
|
return;
|
||||||
$"{from.AccessLevel} {CommandLogging.Format(from)} getting all followers of {CommandLogging.Format(pm)}"
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (pets.Count == 1)
|
CommandLogging.WriteLine(
|
||||||
{
|
from,
|
||||||
from.SendMessage($"That player has {pets.Count} pet.");
|
$"{from.AccessLevel} {CommandLogging.Format(from)} getting all followers of {CommandLogging.Format(pm)}"
|
||||||
}
|
);
|
||||||
else
|
|
||||||
{
|
|
||||||
from.SendMessage($"That player has {pets.Count} pets.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < pets.Count; ++i)
|
if (pets.Count == 1)
|
||||||
{
|
{
|
||||||
var pet = pets[i];
|
from.SendMessage($"That player has {pets.Count} pet.");
|
||||||
|
|
||||||
if (pet is IMount mount)
|
|
||||||
{
|
|
||||||
mount.Rider = null; // make sure it's dismounted
|
|
||||||
}
|
|
||||||
|
|
||||||
pet.MoveToWorld(from.Location, from.Map);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
from.SendMessage("There were no pets found for that player.");
|
from.SendMessage($"That player has {pets.Count} pets.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pet in pets)
|
||||||
|
{
|
||||||
|
if (pet is IMount mount)
|
||||||
|
{
|
||||||
|
mount.Rider = null; // make sure it's dismounted
|
||||||
|
}
|
||||||
|
|
||||||
|
pet.MoveToWorld(from.Location, from.Map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (obj is Mobile master && master.Player)
|
else if (obj is Mobile master && master.Player)
|
||||||
|
|
|
||||||
|
|
@ -867,7 +867,7 @@ public partial class ConditionTeleporter : Teleporter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetFlag(ConditionFlag.DenyFollowers) &&
|
if (GetFlag(ConditionFlag.DenyFollowers) &&
|
||||||
(m.Followers != 0 || m is PlayerMobile mobile && mobile.AutoStabled.Count != 0))
|
(m.Followers != 0 || m is PlayerMobile mobile && mobile.AutoStabled?.Count != 0))
|
||||||
{
|
{
|
||||||
m.SendLocalizedMessage(1077250); // No pets permitted beyond this point.
|
m.SendLocalizedMessage(1077250); // No pets permitted beyond this point.
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ public partial class BallOfSummoning : Item, TranslocationItem
|
||||||
{
|
{
|
||||||
var pet = Pet;
|
var pet = Pet;
|
||||||
|
|
||||||
if (Deleted || pet == null || RootParent != from)
|
if (Deleted || pet == null || RootParent != from || from is not PlayerMobile pm)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +186,7 @@ public partial class BallOfSummoning : Item, TranslocationItem
|
||||||
// The Crystal Ball fills with a blue mist. Your pet is not responding to the summons.
|
// The Crystal Ball fills with a blue mist. Your pet is not responding to the summons.
|
||||||
this.SendLocalizedMessageTo(from, 1054125, 0x5);
|
this.SendLocalizedMessageTo(from, 1054125, 0x5);
|
||||||
}
|
}
|
||||||
else if ((!pet.Controlled || pet.ControlMaster != from) && !from.Stabled.Contains(pet))
|
else if ((!pet.Controlled || pet.ControlMaster != from) && pm.Stabled?.Contains(pet) != true)
|
||||||
{
|
{
|
||||||
// The Crystal Ball fills with a grey mist. You are not the owner of the pet you are attempting to summon.
|
// The Crystal Ball fills with a grey mist. You are not the owner of the pet you are attempting to summon.
|
||||||
this.SendLocalizedMessageTo(from, 1054126, 0x8FD);
|
this.SendLocalizedMessageTo(from, 1054126, 0x8FD);
|
||||||
|
|
@ -221,7 +221,7 @@ public partial class BallOfSummoning : Item, TranslocationItem
|
||||||
{
|
{
|
||||||
var pet = Pet;
|
var pet = Pet;
|
||||||
|
|
||||||
if (pet == null)
|
if (pet == null || from is not PlayerMobile pm)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -242,12 +242,8 @@ public partial class BallOfSummoning : Item, TranslocationItem
|
||||||
|
|
||||||
pet.IsStabled = false;
|
pet.IsStabled = false;
|
||||||
pet.StabledBy = null;
|
pet.StabledBy = null;
|
||||||
from.Stabled.Remove(pet);
|
pm.RemoveStabled(pet);
|
||||||
|
pm.AutoStabled?.Remove(pet);
|
||||||
if (from is PlayerMobile mobile)
|
|
||||||
{
|
|
||||||
mobile.AutoStabled.Remove(pet);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pet.MoveToWorld(from.Location, from.Map);
|
pet.MoveToWorld(from.Location, from.Map);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ public abstract partial class MonsterAbility
|
||||||
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
|
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
|
||||||
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
|
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
|
||||||
|
|
||||||
|
// To prevent reflect from harming the monster
|
||||||
|
public virtual bool CanTriggerAgainstSelf => false;
|
||||||
|
|
||||||
public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
|
public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -54,7 +57,8 @@ public abstract partial class MonsterAbility
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
|
public virtual void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
|
||||||
{
|
{
|
||||||
if (MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero)
|
if (!CanTriggerAgainstSelf && target == source ||
|
||||||
|
MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2082,23 +2082,9 @@ namespace Server.Mobiles
|
||||||
OwnerAbandonTime = reader.ReadDateTime();
|
OwnerAbandonTime = reader.ReadDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version >= 11)
|
m_HasGeneratedLoot = version < 11 || reader.ReadBool();
|
||||||
{
|
|
||||||
m_HasGeneratedLoot = reader.ReadBool();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_HasGeneratedLoot = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version >= 12)
|
m_Paragon = version >= 12 && reader.ReadBool();
|
||||||
{
|
|
||||||
m_Paragon = reader.ReadBool();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_Paragon = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version >= 13 && reader.ReadBool())
|
if (version >= 13 && reader.ReadBool())
|
||||||
{
|
{
|
||||||
|
|
@ -2275,30 +2261,17 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
if (m_ControlMaster != null)
|
if (m_ControlMaster != null)
|
||||||
{
|
{
|
||||||
m_ControlMaster.Followers -= ControlSlots;
|
m_ControlMaster.Followers -= Math.Min(ControlSlots, m_ControlMaster.Followers);
|
||||||
if (m_ControlMaster is PlayerMobile mobile)
|
if (m_ControlMaster is PlayerMobile mobile)
|
||||||
{
|
{
|
||||||
mobile.AllFollowers.Remove(this);
|
mobile.AllFollowers?.Remove(this);
|
||||||
if (mobile.AutoStabled.Contains(this))
|
mobile.AutoStabled?.Remove(this);
|
||||||
{
|
|
||||||
mobile.AutoStabled.Remove(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_SummonMaster != null)
|
else if (m_SummonMaster != null)
|
||||||
{
|
{
|
||||||
m_SummonMaster.Followers -= ControlSlots;
|
m_SummonMaster.Followers -= Math.Min(ControlSlots, m_SummonMaster.Followers);
|
||||||
(m_SummonMaster as PlayerMobile)?.AllFollowers.Remove(this);
|
(m_SummonMaster as PlayerMobile)?.AllFollowers?.Remove(this);
|
||||||
}
|
|
||||||
|
|
||||||
if (m_ControlMaster?.Followers < 0)
|
|
||||||
{
|
|
||||||
m_ControlMaster.Followers = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_SummonMaster?.Followers < 0)
|
|
||||||
{
|
|
||||||
m_SummonMaster.Followers = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2307,17 +2280,17 @@ namespace Server.Mobiles
|
||||||
if (m_ControlMaster != null)
|
if (m_ControlMaster != null)
|
||||||
{
|
{
|
||||||
m_ControlMaster.Followers += ControlSlots;
|
m_ControlMaster.Followers += ControlSlots;
|
||||||
if (m_ControlMaster is PlayerMobile mobile)
|
if (m_ControlMaster is PlayerMobile pm)
|
||||||
{
|
{
|
||||||
mobile.AllFollowers.Add(this);
|
pm.AddFollower(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_SummonMaster != null)
|
else if (m_SummonMaster != null)
|
||||||
{
|
{
|
||||||
m_SummonMaster.Followers += ControlSlots;
|
m_SummonMaster.Followers += ControlSlots;
|
||||||
if (m_SummonMaster is PlayerMobile mobile)
|
if (m_SummonMaster is PlayerMobile pm)
|
||||||
{
|
{
|
||||||
mobile.AllFollowers.Add(this);
|
pm.AddFollower(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2407,6 +2380,8 @@ namespace Server.Mobiles
|
||||||
MLQuestSystem.HandleDeletion(this);
|
MLQuestSystem.HandleDeletion(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnsummonTimer.StopTimer(this);
|
||||||
|
|
||||||
base.OnAfterDelete();
|
base.OnAfterDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3440,6 +3415,8 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemoveFollowers();
|
||||||
|
|
||||||
base.OnDeath(c);
|
base.OnDeath(c);
|
||||||
|
|
||||||
if (DeleteCorpseOnDeath)
|
if (DeleteCorpseOnDeath)
|
||||||
|
|
@ -3455,6 +3432,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
SummonMaster = null;
|
SummonMaster = null;
|
||||||
ReceivedHonorContext?.Cancel();
|
ReceivedHonorContext?.Cancel();
|
||||||
|
|
||||||
base.OnDelete();
|
base.OnDelete();
|
||||||
m?.InvalidateProperties();
|
m?.InvalidateProperties();
|
||||||
}
|
}
|
||||||
|
|
@ -3834,11 +3812,10 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public static void TeleportPets(Mobile master, Point3D loc, Map map, bool onlyBonded = false)
|
public static void TeleportPets(Mobile master, Point3D loc, Map map, bool onlyBonded = false)
|
||||||
{
|
{
|
||||||
if (master is PlayerMobile pm)
|
if (master is PlayerMobile { AllFollowers: not null } pm)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < pm.AllFollowers.Count; i++)
|
foreach (var m in pm.AllFollowers)
|
||||||
{
|
{
|
||||||
var m = pm.AllFollowers[i];
|
|
||||||
if (m.Map == master.Map && master.InRange(m, 3) && m is BaseCreature
|
if (m.Map == master.Map && master.InRange(m, 3) && m is BaseCreature
|
||||||
{ Controlled: true, ControlOrder: OrderType.Guard or OrderType.Follow or OrderType.Come } pet &&
|
{ Controlled: true, ControlOrder: OrderType.Guard or OrderType.Follow or OrderType.Come } pet &&
|
||||||
pet.ControlMaster == master && (!onlyBonded || pet.IsBonded))
|
pet.ControlMaster == master && (!onlyBonded || pet.IsBonded))
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
private Dictionary<int, bool> m_AcquiredRecipes;
|
private Dictionary<int, bool> m_AcquiredRecipes;
|
||||||
|
|
||||||
private List<Mobile> m_AllFollowers;
|
private HashSet<Mobile> _allFollowers;
|
||||||
private int m_BeardModID = -1, m_BeardModHue;
|
private int m_BeardModID = -1, m_BeardModHue;
|
||||||
|
|
||||||
// TODO: Pool BuffInfo objects
|
// TODO: Pool BuffInfo objects
|
||||||
|
|
@ -181,7 +181,6 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
private DateTime m_NextJustAward;
|
private DateTime m_NextJustAward;
|
||||||
|
|
||||||
private int m_NextMovementTime;
|
|
||||||
private int m_NextProtectionCheck = 10;
|
private int m_NextProtectionCheck = 10;
|
||||||
private DateTime m_NextSmithBulkOrder;
|
private DateTime m_NextSmithBulkOrder;
|
||||||
private DateTime m_NextTailorBulkOrder;
|
private DateTime m_NextTailorBulkOrder;
|
||||||
|
|
@ -200,8 +199,6 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public PlayerMobile()
|
public PlayerMobile()
|
||||||
{
|
{
|
||||||
AutoStabled = new List<Mobile>();
|
|
||||||
|
|
||||||
VisibilityList = new List<Mobile>();
|
VisibilityList = new List<Mobile>();
|
||||||
PermaFlags = new List<Mobile>();
|
PermaFlags = new List<Mobile>();
|
||||||
RecentlyReported = new List<Mobile>();
|
RecentlyReported = new List<Mobile>();
|
||||||
|
|
@ -384,11 +381,16 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public List<Mobile> RecentlyReported { get; set; }
|
public List<Mobile> RecentlyReported { get; set; }
|
||||||
|
|
||||||
public List<Mobile> AutoStabled { get; private set; }
|
// WARNING - This can be null!!
|
||||||
|
public HashSet<Mobile> Stabled { get; private set; }
|
||||||
|
|
||||||
|
// WARNING - This can be null!!
|
||||||
|
public HashSet<Mobile> AutoStabled { get; private set; }
|
||||||
|
|
||||||
public bool NinjaWepCooldown { get; set; }
|
public bool NinjaWepCooldown { get; set; }
|
||||||
|
|
||||||
public List<Mobile> AllFollowers => m_AllFollowers ??= new List<Mobile>();
|
// WARNING - This can be null!!
|
||||||
|
public HashSet<Mobile> AllFollowers => _allFollowers;
|
||||||
|
|
||||||
public RankDefinition GuildRank
|
public RankDefinition GuildRank
|
||||||
{
|
{
|
||||||
|
|
@ -961,6 +963,18 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
Timer.StartTimer(CheckPets);
|
Timer.StartTimer(CheckPets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stableMigrations = StableMigrations;
|
||||||
|
if (stableMigrations?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var (player, stabled) in stableMigrations)
|
||||||
|
{
|
||||||
|
if (player is PlayerMobile pm)
|
||||||
|
{
|
||||||
|
pm.Stabled = stabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void TargetedSkillUse(Mobile from, IEntity target, int skillId)
|
private static void TargetedSkillUse(Mobile from, IEntity target, int skillId)
|
||||||
|
|
@ -1062,8 +1076,8 @@ namespace Server.Mobiles
|
||||||
foreach (var m in World.Mobiles.Values)
|
foreach (var m in World.Mobiles.Values)
|
||||||
{
|
{
|
||||||
if (m is PlayerMobile pm &&
|
if (m is PlayerMobile pm &&
|
||||||
((!pm.Mounted || pm.Mount is EtherealMount) && pm.AllFollowers.Count > pm.AutoStabled.Count ||
|
((!pm.Mounted || pm.Mount is EtherealMount) && pm.AllFollowers?.Count > pm.AutoStabled?.Count ||
|
||||||
pm.Mounted && pm.AllFollowers.Count > pm.AutoStabled.Count + 1))
|
pm.Mounted && pm.AllFollowers?.Count > (pm.AutoStabled?.Count ?? 0) + 1))
|
||||||
{
|
{
|
||||||
pm.AutoStablePets(); /* autostable checks summons, et al: no need here */
|
pm.AutoStablePets(); /* autostable checks summons, et al: no need here */
|
||||||
}
|
}
|
||||||
|
|
@ -1723,7 +1737,7 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var speed = ComputeMovementSpeed(d);
|
// var speed = ComputeMovementSpeed(d);
|
||||||
|
|
||||||
if (!Alive)
|
if (!Alive)
|
||||||
{
|
{
|
||||||
|
|
@ -1731,17 +1745,8 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = base.Move(d);
|
var res = base.Move(d);
|
||||||
|
|
||||||
MovementImpl.IgnoreMovableImpassables = false;
|
MovementImpl.IgnoreMovableImpassables = false;
|
||||||
|
return res;
|
||||||
if (!res)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_NextMovementTime += speed;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CheckMovement(Direction d, out int newZ)
|
public override bool CheckMovement(Direction d, out int newZ)
|
||||||
|
|
@ -2880,6 +2885,11 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
switch (version)
|
switch (version)
|
||||||
{
|
{
|
||||||
|
case 30:
|
||||||
|
{
|
||||||
|
Stabled = reader.ReadEntitySet<Mobile>(true);
|
||||||
|
goto case 29;
|
||||||
|
}
|
||||||
case 29:
|
case 29:
|
||||||
{
|
{
|
||||||
if (reader.ReadBool())
|
if (reader.ReadBool())
|
||||||
|
|
@ -2912,7 +2922,7 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
case 26:
|
case 26:
|
||||||
{
|
{
|
||||||
AutoStabled = reader.ReadEntityList<Mobile>();
|
AutoStabled = reader.ReadEntitySet<Mobile>(true);
|
||||||
|
|
||||||
goto case 25;
|
goto case 25;
|
||||||
}
|
}
|
||||||
|
|
@ -3129,11 +3139,6 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
case 0:
|
case 0:
|
||||||
{
|
{
|
||||||
if (version < 26)
|
|
||||||
{
|
|
||||||
AutoStabled = new List<Mobile>();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3164,14 +3169,15 @@ namespace Server.Mobiles
|
||||||
IgnoreMobiles = true;
|
IgnoreMobiles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = Stabled;
|
if (Stabled != null)
|
||||||
|
|
||||||
for (var i = 0; i < list.Count; ++i)
|
|
||||||
{
|
{
|
||||||
if (list[i] is BaseCreature bc)
|
foreach (var stabled in Stabled)
|
||||||
{
|
{
|
||||||
bc.IsStabled = true;
|
if (stabled is BaseCreature bc)
|
||||||
bc.StabledBy = this;
|
{
|
||||||
|
bc.IsStabled = true;
|
||||||
|
bc.StabledBy = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3188,7 +3194,17 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
base.Serialize(writer);
|
base.Serialize(writer);
|
||||||
|
|
||||||
writer.Write(29); // version
|
writer.Write(30); // version
|
||||||
|
|
||||||
|
if (Stabled == null)
|
||||||
|
{
|
||||||
|
writer.Write(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stabled.Tidy();
|
||||||
|
writer.Write(Stabled);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_StuckMenuUses != null)
|
if (m_StuckMenuUses != null)
|
||||||
{
|
{
|
||||||
|
|
@ -3208,8 +3224,15 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
writer.Write(PeacedUntil);
|
writer.Write(PeacedUntil);
|
||||||
writer.Write(AnkhNextUse);
|
writer.Write(AnkhNextUse);
|
||||||
AutoStabled.Tidy();
|
if (AutoStabled == null)
|
||||||
writer.Write(AutoStabled);
|
{
|
||||||
|
writer.Write(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoStabled.Tidy();
|
||||||
|
writer.Write(AutoStabled);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_AcquiredRecipes == null)
|
if (m_AcquiredRecipes == null)
|
||||||
{
|
{
|
||||||
|
|
@ -3480,11 +3503,11 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Core.ML)
|
if (Core.ML && AllFollowers != null)
|
||||||
{
|
{
|
||||||
for (var i = AllFollowers.Count - 1; i >= 0; i--)
|
foreach (var follower in AllFollowers)
|
||||||
{
|
{
|
||||||
if (AllFollowers[i] is BaseCreature c && c.ControlOrder == OrderType.Guard)
|
if (follower is BaseCreature { ControlOrder: OrderType.Guard })
|
||||||
{
|
{
|
||||||
list.Add(501129); // guarded
|
list.Add(501129); // guarded
|
||||||
break;
|
break;
|
||||||
|
|
@ -3576,79 +3599,127 @@ namespace Server.Mobiles
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddFollower(Mobile m)
|
||||||
|
{
|
||||||
|
_allFollowers ??= new HashSet<Mobile>();
|
||||||
|
_allFollowers.Add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddStabled(Mobile m)
|
||||||
|
{
|
||||||
|
Stabled ??= new HashSet<Mobile>();
|
||||||
|
Stabled.Add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveStabled(Mobile m)
|
||||||
|
{
|
||||||
|
if (Stabled?.Remove(m) == true)
|
||||||
|
{
|
||||||
|
if (Stabled.Count == 0)
|
||||||
|
{
|
||||||
|
Stabled = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveFollower(Mobile m)
|
||||||
|
{
|
||||||
|
if (_allFollowers?.Remove(m) == true)
|
||||||
|
{
|
||||||
|
if (_allFollowers.Count == 0)
|
||||||
|
{
|
||||||
|
_allFollowers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void AutoStablePets()
|
public void AutoStablePets()
|
||||||
{
|
{
|
||||||
if (Core.SE && AllFollowers.Count > 0)
|
var allFollowers = _allFollowers;
|
||||||
|
|
||||||
|
if (!Core.SE || !(allFollowers?.Count > 0))
|
||||||
{
|
{
|
||||||
for (var i = m_AllFollowers.Count - 1; i >= 0; --i)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var follower in allFollowers)
|
||||||
|
{
|
||||||
|
if (follower is not BaseCreature pet || pet.ControlMaster == null)
|
||||||
{
|
{
|
||||||
if (AllFollowers[i] is not BaseCreature pet || pet.ControlMaster == null)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pet.Summoned)
|
|
||||||
{
|
|
||||||
if (pet.Map != Map)
|
|
||||||
{
|
|
||||||
pet.PlaySound(pet.GetAngerSound());
|
|
||||||
Timer.StartTimer(pet.Delete);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((pet as IMount)?.Rider != null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pet is PackLlama or PackHorse or Beetle && pet.Backpack?.Items.Count > 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pet is BaseEscortable)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pet.ControlTarget = null;
|
|
||||||
pet.ControlOrder = OrderType.Stay;
|
|
||||||
pet.Internalize();
|
|
||||||
|
|
||||||
pet.SetControlMaster(null);
|
|
||||||
pet.SummonMaster = null;
|
|
||||||
|
|
||||||
pet.IsStabled = true;
|
|
||||||
pet.StabledBy = this;
|
|
||||||
|
|
||||||
pet.Loyalty = BaseCreature.MaxLoyalty; // Wonderfully happy
|
|
||||||
|
|
||||||
Stabled.Add(pet);
|
|
||||||
AutoStabled.Add(pet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pet.Summoned)
|
||||||
|
{
|
||||||
|
if (pet.Map != Map)
|
||||||
|
{
|
||||||
|
pet.PlaySound(pet.GetAngerSound());
|
||||||
|
Timer.StartTimer(pet.Delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pet as IMount)?.Rider != null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pet is PackLlama or PackHorse or Beetle && pet.Backpack?.Items.Count > 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pet is BaseEscortable)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pet.ControlTarget = null;
|
||||||
|
pet.ControlOrder = OrderType.Stay;
|
||||||
|
pet.Internalize();
|
||||||
|
|
||||||
|
pet.SetControlMaster(null);
|
||||||
|
pet.SummonMaster = null;
|
||||||
|
|
||||||
|
pet.IsStabled = true;
|
||||||
|
pet.StabledBy = this;
|
||||||
|
|
||||||
|
pet.Loyalty = BaseCreature.MaxLoyalty; // Wonderfully happy
|
||||||
|
|
||||||
|
Stabled ??= new HashSet<Mobile>();
|
||||||
|
Stabled.Add(pet);
|
||||||
|
|
||||||
|
AutoStabled ??= new HashSet<Mobile>();
|
||||||
|
AutoStabled.Add(pet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClaimAutoStabledPets()
|
public void ClaimAutoStabledPets()
|
||||||
{
|
{
|
||||||
if (!Core.SE || AutoStabled.Count <= 0)
|
if (!Core.SE || !(AutoStabled?.Count > 0))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Alive)
|
if (!Alive)
|
||||||
{
|
{
|
||||||
SendLocalizedMessage(
|
// Your pet was unable to join you while you are a ghost. Please re-login once you have ressurected to claim your pets.
|
||||||
1076251
|
SendLocalizedMessage(1076251);
|
||||||
); // Your pet was unable to join you while you are a ghost. Please re-login once you have ressurected to claim your pets.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = AutoStabled.Count - 1; i >= 0; --i)
|
foreach (var stabled in AutoStabled)
|
||||||
{
|
{
|
||||||
if (AutoStabled[i] is not BaseCreature pet)
|
if (stabled is not BaseCreature pet)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -3658,11 +3729,7 @@ namespace Server.Mobiles
|
||||||
pet.IsStabled = false;
|
pet.IsStabled = false;
|
||||||
pet.StabledBy = null;
|
pet.StabledBy = null;
|
||||||
|
|
||||||
if (Stabled.Contains(pet))
|
Stabled?.Remove(pet);
|
||||||
{
|
|
||||||
Stabled.Remove(pet);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3685,21 +3752,16 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
pet.Loyalty = BaseCreature.MaxLoyalty; // Wonderfully Happy
|
pet.Loyalty = BaseCreature.MaxLoyalty; // Wonderfully Happy
|
||||||
|
|
||||||
if (Stabled.Contains(pet))
|
Stabled?.Remove(pet);
|
||||||
{
|
|
||||||
Stabled.Remove(pet);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SendLocalizedMessage(
|
// ~1_NAME~ remained in the stables because you have too many followers.
|
||||||
1049612,
|
SendLocalizedMessage(1049612, pet.Name);
|
||||||
pet.Name
|
|
||||||
); // ~1_NAME~ remained in the stables because you have too many followers.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoStabled.Clear();
|
AutoStabled = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RecoverAmmo()
|
public void RecoverAmmo()
|
||||||
|
|
@ -4144,6 +4206,16 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
ReceivedHonorContext?.Cancel();
|
ReceivedHonorContext?.Cancel();
|
||||||
SentHonorContext?.Cancel();
|
SentHonorContext?.Cancel();
|
||||||
|
|
||||||
|
if (Stabled != null)
|
||||||
|
{
|
||||||
|
foreach (var stabled in Stabled)
|
||||||
|
{
|
||||||
|
stabled.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stabled = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int ComputeMovementSpeed(Direction dir, bool checkTurning = true)
|
public override int ComputeMovementSpeed(Direction dir, bool checkTurning = true)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using ModernUO.Serialization;
|
using ModernUO.Serialization;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Server.Collections;
|
||||||
using Server.ContextMenus;
|
using Server.ContextMenus;
|
||||||
using Server.Gumps;
|
using Server.Gumps;
|
||||||
using Server.Items;
|
using Server.Items;
|
||||||
|
|
@ -41,11 +42,11 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public override void AddCustomContextEntries(Mobile from, List<ContextMenuEntry> list)
|
public override void AddCustomContextEntries(Mobile from, List<ContextMenuEntry> list)
|
||||||
{
|
{
|
||||||
if (from.Alive)
|
if (from is PlayerMobile { Alive: true } pm)
|
||||||
{
|
{
|
||||||
list.Add(new StableEntry(this, from));
|
list.Add(new StableEntry(this, from));
|
||||||
|
|
||||||
if (from.Stabled.Count > 0)
|
if (pm.Stabled?.Count > 0)
|
||||||
{
|
{
|
||||||
list.Add(new ClaimAllEntry(this, from));
|
list.Add(new ClaimAllEntry(this, from));
|
||||||
}
|
}
|
||||||
|
|
@ -105,31 +106,38 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public void BeginClaimList(Mobile from)
|
public void BeginClaimList(Mobile from)
|
||||||
{
|
{
|
||||||
if (Deleted || !from.CheckAlive())
|
if (Deleted || !from.CheckAlive() || from is not PlayerMobile pm)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<BaseCreature>();
|
var list = new List<BaseCreature>();
|
||||||
|
|
||||||
for (var i = 0; i < from.Stabled.Count; ++i)
|
if (pm.Stabled?.Count > 0)
|
||||||
{
|
{
|
||||||
var pet = from.Stabled[i] as BaseCreature;
|
using var queue = PooledRefQueue<Mobile>.Create();
|
||||||
|
|
||||||
if (pet?.Deleted != false)
|
foreach (var m in pm.Stabled)
|
||||||
{
|
{
|
||||||
if (pet != null)
|
if (m is BaseCreature pet)
|
||||||
{
|
{
|
||||||
|
if (!pet.Deleted)
|
||||||
|
{
|
||||||
|
list.Add(pet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
pet.IsStabled = false;
|
pet.IsStabled = false;
|
||||||
pet.StabledBy = null;
|
pet.StabledBy = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
from.Stabled.RemoveAt(i);
|
queue.Enqueue(m);
|
||||||
--i;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(pet);
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
pm.RemoveStabled(queue.Dequeue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.Count > 0)
|
if (list.Count > 0)
|
||||||
|
|
@ -144,7 +152,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public void EndClaimList(Mobile from, BaseCreature pet)
|
public void EndClaimList(Mobile from, BaseCreature pet)
|
||||||
{
|
{
|
||||||
if (pet?.Deleted != false || from.Map != Map || !from.Stabled.Contains(pet) || !from.CheckAlive())
|
if (pet?.Deleted != false || from.Map != Map || from is not PlayerMobile pm || pm.Stabled?.Contains(pet) != true || !from.CheckAlive())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -159,9 +167,8 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
DoClaim(from, pet);
|
DoClaim(from, pet);
|
||||||
|
|
||||||
from.Stabled.Remove(pet);
|
pm.RemoveStabled(pet);
|
||||||
|
pm.AutoStabled?.Remove(pet);
|
||||||
(from as PlayerMobile)?.AutoStabled.Remove(pet);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -176,8 +183,6 @@ namespace Server.Mobiles
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Container bank = from.FindBankNoCreate();
|
|
||||||
|
|
||||||
if (!(from.Backpack?.GetAmount(typeof(Gold)) >= 30) &&
|
if (!(from.Backpack?.GetAmount(typeof(Gold)) >= 30) &&
|
||||||
!(Banker.GetBalance(from) >= 30))
|
!(Banker.GetBalance(from) >= 30))
|
||||||
{
|
{
|
||||||
|
|
@ -197,7 +202,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public void EndStable(Mobile from, BaseCreature pet)
|
public void EndStable(Mobile from, BaseCreature pet)
|
||||||
{
|
{
|
||||||
if (Deleted || !from.CheckAlive())
|
if (Deleted || !from.CheckAlive() || from is not PlayerMobile pm)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -236,14 +241,12 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
SayTo(from, 1042564); // I'm sorry. Your pet seems to be busy.
|
SayTo(from, 1042564); // I'm sorry. Your pet seems to be busy.
|
||||||
}
|
}
|
||||||
else if (from.Stabled.Count >= GetMaxStabled(from))
|
else if (pm.Stabled?.Count >= GetMaxStabled(from))
|
||||||
{
|
{
|
||||||
SayTo(from, 1042565); // You have too many pets in the stables!
|
SayTo(from, 1042565); // You have too many pets in the stables!
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Container bank = from.FindBankNoCreate();
|
|
||||||
|
|
||||||
if (from.Backpack?.ConsumeTotal(typeof(Gold), 30) == true || Banker.Withdraw(from, 30))
|
if (from.Backpack?.ConsumeTotal(typeof(Gold), 30) == true || Banker.Withdraw(from, 30))
|
||||||
{
|
{
|
||||||
pet.ControlTarget = null;
|
pet.ControlTarget = null;
|
||||||
|
|
@ -261,14 +264,10 @@ namespace Server.Mobiles
|
||||||
pet.Loyalty = MaxLoyalty; // Wonderfully happy
|
pet.Loyalty = MaxLoyalty; // Wonderfully happy
|
||||||
}
|
}
|
||||||
|
|
||||||
from.Stabled.Add(pet);
|
pm.AddStabled(pet);
|
||||||
|
|
||||||
SayTo(
|
// [AOS: Your pet has been stabled.] Very well, thy pet is stabled. Thou mayst recover it by saying 'claim' to me. In one real world week, I shall sell it off if it is not claimed!
|
||||||
from,
|
SayTo(from, Core.AOS ? 1049677 : 502679);
|
||||||
Core.AOS
|
|
||||||
? 1049677
|
|
||||||
: 502679
|
|
||||||
); // [AOS: Your pet has been stabled.] Very well, thy pet is stabled. Thou mayst recover it by saying 'claim' to me. In one real world week, I shall sell it off if it is not claimed!
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -279,7 +278,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public void Claim(Mobile from, string petName = null)
|
public void Claim(Mobile from, string petName = null)
|
||||||
{
|
{
|
||||||
if (Deleted || !from.CheckAlive())
|
if (Deleted || !from.CheckAlive() || from is not PlayerMobile pm)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -289,44 +288,51 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
var claimByName = petName != null;
|
var claimByName = petName != null;
|
||||||
|
|
||||||
for (var i = 0; i < from.Stabled.Count; ++i)
|
if (pm.Stabled?.Count > 0)
|
||||||
{
|
{
|
||||||
var pet = from.Stabled[i] as BaseCreature;
|
using var queue = PooledRefQueue<Mobile>.Create();
|
||||||
|
|
||||||
if (pet?.Deleted != false)
|
foreach (var m in pm.Stabled)
|
||||||
{
|
{
|
||||||
if (pet != null)
|
var pet = m as BaseCreature;
|
||||||
|
|
||||||
|
if (pet?.Deleted != false)
|
||||||
{
|
{
|
||||||
pet.IsStabled = false;
|
if (pet != null)
|
||||||
pet.StabledBy = null;
|
{
|
||||||
|
pet.IsStabled = false;
|
||||||
|
pet.StabledBy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.Enqueue(pet);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++stabled;
|
||||||
|
|
||||||
|
if (claimByName && !pet.Name.InsensitiveEquals(petName))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanClaim(from, pet))
|
||||||
|
{
|
||||||
|
DoClaim(from, pet);
|
||||||
|
|
||||||
|
queue.Enqueue(pet);
|
||||||
|
|
||||||
|
claimed = true;
|
||||||
|
pm.AutoStabled?.Remove(pet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SayTo(from, 1049612, pet.Name); // ~1_NAME~ remained in the stables because you have too many followers.
|
||||||
}
|
}
|
||||||
from.Stabled.RemoveAt(i);
|
|
||||||
--i;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
++stabled;
|
while (queue.Count > 0)
|
||||||
|
|
||||||
if (claimByName && !pet.Name.InsensitiveEquals(petName))
|
|
||||||
{
|
{
|
||||||
continue;
|
pm.RemoveStabled(queue.Dequeue());
|
||||||
}
|
|
||||||
|
|
||||||
if (CanClaim(from, pet))
|
|
||||||
{
|
|
||||||
DoClaim(from, pet);
|
|
||||||
|
|
||||||
from.Stabled.RemoveAt(i);
|
|
||||||
|
|
||||||
(from as PlayerMobile)?.AutoStabled.Remove(pet);
|
|
||||||
|
|
||||||
--i;
|
|
||||||
|
|
||||||
claimed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SayTo(from, 1049612, pet.Name); // ~1_NAME~ remained in the stables because you have too many followers.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,9 +239,7 @@ namespace Server.Spells.Necromancy
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Remove(summoned);
|
if (list.Remove(summoned) && list.Count == 0)
|
||||||
|
|
||||||
if (list.Count == 0)
|
|
||||||
{
|
{
|
||||||
_table.Remove(master);
|
_table.Remove(master);
|
||||||
}
|
}
|
||||||
|
|
@ -278,10 +276,17 @@ namespace Server.Spells.Necromancy
|
||||||
|
|
||||||
if (list.Count > 3)
|
if (list.Count > 3)
|
||||||
{
|
{
|
||||||
Timer.StartTimer(list[0].Kill);
|
var toKill = list[0];
|
||||||
|
Unregister(master, toKill);
|
||||||
|
toKill.Kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer.StartTimer(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0), () => Summoned_Damage(summoned));
|
Timer.DelayCall(
|
||||||
|
TimeSpan.FromMilliseconds(1650),
|
||||||
|
TimeSpan.FromMilliseconds(1650),
|
||||||
|
Summoned_Damage,
|
||||||
|
summoned
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Summoned_Damage(Mobile mob)
|
private static void Summoned_Damage(Mobile mob)
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ namespace Server.Mobiles
|
||||||
ControlOrder = OrderType.Follow;
|
ControlOrder = OrderType.Follow;
|
||||||
ControlTarget = caster;
|
ControlTarget = caster;
|
||||||
|
|
||||||
var duration = TimeSpan.FromSeconds(30 + caster.Skills.Ninjitsu.Fixed / 40);
|
var duration = TimeSpan.FromSeconds(30.0 + caster.Skills.Ninjitsu.Value / 4.0);
|
||||||
|
|
||||||
new UnsummonTimer(this, duration).Start();
|
new UnsummonTimer(this, duration).Start();
|
||||||
SummonEnd = Core.Now + duration;
|
SummonEnd = Core.Now + duration;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,40 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using Server.Mobiles;
|
using Server.Mobiles;
|
||||||
|
|
||||||
namespace Server.Spells
|
namespace Server.Spells;
|
||||||
|
|
||||||
|
public class UnsummonTimer : Timer
|
||||||
{
|
{
|
||||||
internal class UnsummonTimer : Timer
|
// Track timers since some of them are really long and might hold references to long dead/deleted mobs
|
||||||
|
private static readonly Dictionary<BaseCreature, UnsummonTimer> _timers = new();
|
||||||
|
private BaseCreature _creature;
|
||||||
|
|
||||||
|
public static void StopTimer(BaseCreature creature)
|
||||||
{
|
{
|
||||||
private readonly BaseCreature m_Creature;
|
if (_timers.Remove(creature, out var timer))
|
||||||
|
|
||||||
public UnsummonTimer(BaseCreature creature, TimeSpan delay) : base(delay)
|
|
||||||
{
|
{
|
||||||
m_Creature = creature;
|
timer.Stop();
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnTick()
|
|
||||||
{
|
|
||||||
if (!m_Creature.Deleted)
|
|
||||||
{
|
|
||||||
m_Creature.Delete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UnsummonTimer(BaseCreature creature, TimeSpan delay) : base(delay)
|
||||||
|
{
|
||||||
|
_creature = creature;
|
||||||
|
|
||||||
|
ref var timer = ref CollectionsMarshal.GetValueRefOrAddDefault(_timers, creature, out bool exists);
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTick()
|
||||||
|
{
|
||||||
|
// BaseCreature.OnAfterDelete will remove the creature from the timers table
|
||||||
|
_creature?.Delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue