/*************************************************************************** * Mobile.cs * ------------------- * begin : May 1, 2002 * copyright : (C) The RunUO Software Team * email : info@runuo.com * * $Id$ * ***************************************************************************/ /*************************************************************************** * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * ***************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using Server.Accounting; using Server.ContextMenus; using Server.Guilds; using Server.Gumps; using Server.HuePickers; using Server.Items; using Server.Menus; using Server.Mobiles; using Server.Network; using Server.Prompts; using Server.Targeting; using Server.Utilities; namespace Server { public delegate void TargetCallback(Mobile from, object targeted); public delegate void TargetStateCallback(Mobile from, object targeted, T state); public delegate void PromptCallback(Mobile from, string text); public delegate void PromptStateCallback(Mobile from, string text, T state); public class TimedSkillMod : SkillMod { private readonly DateTime m_Expire; public TimedSkillMod(SkillName skill, bool relative, double value, TimeSpan delay) : this(skill, relative, value, DateTime.UtcNow + delay) { } public TimedSkillMod(SkillName skill, bool relative, double value, DateTime expire) : base(skill, relative, value) => m_Expire = expire; public override bool CheckCondition() => DateTime.UtcNow < m_Expire; } public class EquippedSkillMod : SkillMod { private readonly Item m_Item; private readonly Mobile m_Mobile; public EquippedSkillMod(SkillName skill, bool relative, double value, Item item, Mobile mobile) : base(skill, relative, value) { m_Item = item; m_Mobile = mobile; } public override bool CheckCondition() => !m_Item.Deleted && !m_Mobile.Deleted && m_Item.Parent == m_Mobile; } public class DefaultSkillMod : SkillMod { public DefaultSkillMod(SkillName skill, bool relative, double value) : base(skill, relative, value) { } public override bool CheckCondition() => true; } public abstract class SkillMod { private bool m_ObeyCap; private Mobile m_Owner; private bool m_Relative; private SkillName m_Skill; private double m_Value; protected SkillMod(SkillName skill, bool relative, double value) { m_Skill = skill; m_Relative = relative; m_Value = value; } public bool ObeyCap { get => m_ObeyCap; set { m_ObeyCap = value; var sk = m_Owner?.Skills[m_Skill]; sk?.Update(); } } public Mobile Owner { get => m_Owner; set { if (m_Owner != value) { m_Owner?.RemoveSkillMod(this); m_Owner = value; if (m_Owner != value) m_Owner.AddSkillMod(this); } } } public SkillName Skill { get => m_Skill; set { if (m_Skill != value) { var oldUpdate = m_Owner?.Skills[m_Skill]; m_Skill = value; var sk = m_Owner?.Skills[m_Skill]; sk?.Update(); oldUpdate?.Update(); } } } public bool Relative { get => m_Relative; set { if (m_Relative != value) { m_Relative = value; var sk = m_Owner?.Skills[m_Skill]; sk?.Update(); } } } public bool Absolute { get => !m_Relative; set { if (m_Relative == value) { m_Relative = !value; var sk = m_Owner?.Skills[m_Skill]; sk?.Update(); } } } public double Value { get => m_Value; set { if (m_Value != value) { m_Value = value; var sk = m_Owner?.Skills[m_Skill]; sk?.Update(); } } } public void Remove() { Owner = null; } public abstract bool CheckCondition(); } public class ResistanceMod { private int m_Offset; private ResistanceType m_Type; public ResistanceMod(ResistanceType type, int offset) { m_Type = type; m_Offset = offset; } public Mobile Owner { get; set; } public ResistanceType Type { get => m_Type; set { if (m_Type != value) { m_Type = value; Owner?.UpdateResistances(); } } } public int Offset { get => m_Offset; set { if (m_Offset != value) { m_Offset = value; Owner?.UpdateResistances(); } } } } public class StatMod { private readonly DateTime m_Added; private readonly TimeSpan m_Duration; public StatMod(StatType type, string name, int offset, TimeSpan duration) { Type = type; Name = name; Offset = offset; m_Duration = duration; m_Added = DateTime.UtcNow; } public StatType Type { get; } public string Name { get; } public int Offset { get; } public bool HasElapsed() { if (m_Duration == TimeSpan.Zero) return false; return DateTime.UtcNow - m_Added >= m_Duration; } } public class DamageEntry { public DamageEntry(Mobile damager) => Damager = damager; public Mobile Damager { get; } public int DamageGiven { get; set; } public DateTime LastDamage { get; set; } public bool HasExpired => DateTime.UtcNow > LastDamage + ExpireDelay; public List Responsible { get; set; } public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); } [Flags] public enum StatType { Str = 1, Dex = 2, Int = 4, All = 7 } public enum StatLockType : byte { Up, Down, Locked } [CustomEnum(new[] { "North", "Right", "East", "Down", "South", "Left", "West", "Up" })] [Flags] public enum Direction : byte { North = 0x0, Right = 0x1, East = 0x2, Down = 0x3, South = 0x4, Left = 0x5, West = 0x6, Up = 0x7, Mask = 0x7, Running = 0x80, ValueMask = 0x87 } [Flags] public enum MobileDelta { None = 0x00000000, Name = 0x00000001, Flags = 0x00000002, Hits = 0x00000004, Mana = 0x00000008, Stam = 0x00000010, Stat = 0x00000020, Noto = 0x00000040, Gold = 0x00000080, Weight = 0x00000100, Direction = 0x00000200, Hue = 0x00000400, Body = 0x00000800, Armor = 0x00001000, StatCap = 0x00002000, GhostUpdate = 0x00004000, Followers = 0x00008000, Properties = 0x00010000, TithingPoints = 0x00020000, Resistances = 0x00040000, WeaponDamage = 0x00080000, Hair = 0x00100000, FacialHair = 0x00200000, Race = 0x00400000, HealthbarYellow = 0x00800000, HealthbarPoison = 0x01000000, Attributes = 0x0000001C } public enum AccessLevel { Player, Counselor, GameMaster, Seer, Administrator, Developer, Owner } public enum VisibleDamageType { None, Related, Everyone, Selective } public enum ResistanceType { Physical, Fire, Cold, Poison, Energy } public enum ApplyPoisonResult { Poisoned, Immune, HigherPoisonActive, Cured } [Serializable] public class MobileNotConnectedException : Exception { public MobileNotConnectedException(Mobile source, string message) : base(message) => Source = source.ToString(); public MobileNotConnectedException(Mobile source, string message, Exception innerException) : base(message, innerException) => Source = source.ToString(); protected MobileNotConnectedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } public delegate bool SkillCheckTargetHandler(Mobile from, SkillName skill, object target, double minSkill, double maxSkill); public delegate bool SkillCheckLocationHandler(Mobile from, SkillName skill, double minSkill, double maxSkill); public delegate bool SkillCheckDirectTargetHandler(Mobile from, SkillName skill, object target, double chance); public delegate bool SkillCheckDirectLocationHandler(Mobile from, SkillName skill, double chance); public delegate TimeSpan RegenRateHandler(Mobile from); public delegate bool AllowBeneficialHandler(Mobile from, Mobile target); public delegate bool AllowHarmfulHandler(Mobile from, Mobile target); public delegate Container CreateCorpseHandler(Mobile from, HairInfo hair, FacialHairInfo facialhair, List initialContent, List equippedItems); public delegate int AOSStatusHandler(Mobile from, int index); /// /// Base class representing players, npcs, and creatures. /// public class Mobile : IHued, IComparable, ISerializable, ISpawnable, IPropertyListObject { private readonly BufferWriter m_SaveBuffer; public BufferWriter SaveBuffer => m_SaveBuffer; private const int WarmodeCatchCount = 4; // Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds private static readonly TimeSpan WarmodeSpamCatch = TimeSpan.FromSeconds(Core.SE ? 1.0 : 0.5); private static readonly TimeSpan WarmodeSpamDelay = TimeSpan.FromSeconds(Core.SE ? 4.0 : 2.0); private static readonly Packet[][] m_MovingPacketCache = new Packet[][] { new Packet[8], new Packet[8] }; private static readonly List m_MoveList = new List(); private static readonly List m_MoveClientList = new List(); private static readonly object m_GhostMutateContext = new object(); private static readonly List m_Hears = new List(); private static readonly List m_OnSpeech = new List(); private static readonly string[] m_AccessLevelNames = { "a player", "a counselor", "a game master", "a seer", "an administrator", "a developer", "an owner" }; private static readonly int[] m_InvalidBodies = { 32, 95, 156, 197, 198 }; private static readonly Queue m_DeltaQueue = new Queue(); private static readonly Queue m_DeltaQueueR = new Queue(); private static bool _processing; private static readonly string[] m_GuildTypes = { "", " (Chaos)", " (Order)" }; private Timer m_AutoManifestTimer; private Container m_Backpack; private BankBox m_BankBox; private int m_ChangingCombatant; private MobileDelta m_DeltaFlags; private long m_EndQueue; private Item m_Holding; private int m_HueMod = -1; private bool m_InDeltaQueue; /* Logout: * * When a client logs into mobile x * - if (x is Internalized ) move x to logout location and map * * When a client attached to a mobile disconnects * - LogoutTimer is started * - Delay is taken from Region.GetLogoutDelay to allow insta-logout regions. * - OnTick : Location and map are stored, and mobile is internalized * * Some things to consider: * - An internalized person getting killed (say, by poison). Where does the body go? * - Regions now have a GetLogoutDelay( Mobile m ); virtual function (see above) */ private Item m_MountItem; private string m_NameMod; private QuestArrow m_QuestArrow; private int m_SolidHueOverride = -1; private StatLockType m_StrLock, m_DexLock, m_IntLock; private IWeapon m_Weapon; private bool m_YellowHealthbar; public Mobile(Serial serial) { m_Region = Map.Internal.DefaultRegion; Serial = serial; Aggressors = new List(); Aggressed = new List(); NextSkillTime = Core.TickCount; DamageEntries = new List(); var ourType = GetType(); TypeRef = World.m_MobileTypes.IndexOf(ourType); if (TypeRef == -1) { World.m_MobileTypes.Add(ourType); TypeRef = World.m_MobileTypes.Count - 1; } m_SaveBuffer = new BufferWriter(true); } public Mobile() { m_Region = Map.Internal.DefaultRegion; Serial = Serial.NewMobile; DefaultMobileInit(); World.AddMobile(this); var ourType = GetType(); TypeRef = World.m_MobileTypes.IndexOf(ourType); if (TypeRef == -1) { World.m_MobileTypes.Add(ourType); TypeRef = World.m_MobileTypes.Count - 1; } m_SaveBuffer = new BufferWriter(true); } public static bool DragEffects { get; set; } = true; [CommandProperty(AccessLevel.GameMaster)] public Race Race { get => m_Race ?? (m_Race = Race.DefaultRace); set { var oldRace = Race; m_Race = value ?? Race.DefaultRace; Body = m_Race.Body(this); UpdateResistances(); Delta(MobileDelta.Race); OnRaceChange(oldRace); } } public virtual double RacialSkillBonus => 0; public int[] Resistances { get; private set; } public virtual int BasePhysicalResistance => 0; public virtual int BaseFireResistance => 0; public virtual int BaseColdResistance => 0; public virtual int BasePoisonResistance => 0; public virtual int BaseEnergyResistance => 0; [CommandProperty(AccessLevel.Counselor)] public virtual int PhysicalResistance => GetResistance(ResistanceType.Physical); [CommandProperty(AccessLevel.Counselor)] public virtual int FireResistance => GetResistance(ResistanceType.Fire); [CommandProperty(AccessLevel.Counselor)] public virtual int ColdResistance => GetResistance(ResistanceType.Cold); [CommandProperty(AccessLevel.Counselor)] public virtual int PoisonResistance => GetResistance(ResistanceType.Poison); [CommandProperty(AccessLevel.Counselor)] public virtual int EnergyResistance => GetResistance(ResistanceType.Energy); public List ResistanceMods { get; set; } public static int MaxPlayerResistance { get; set; } = 70; public virtual bool NewGuildDisplay => false; public List Stabled { get; private set; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public VirtueInfo Virtues { get; private set; } public object Party { get; set; } public List SkillMods { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public int VirtualArmorMod { get => m_VirtualArmorMod; set { if (m_VirtualArmorMod != value) { m_VirtualArmorMod = value; Delta(MobileDelta.Armor); } } } [CommandProperty(AccessLevel.GameMaster)] public int MeleeDamageAbsorb { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int MagicDamageAbsorb { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int SkillsTotal => Skills?.Total ?? 0; [CommandProperty(AccessLevel.GameMaster)] public int SkillsCap { get => Skills?.Cap ?? 0; set { if (Skills != null) Skills.Cap = value; } } [CommandProperty(AccessLevel.GameMaster)] public int BaseSoundID { get; set; } public long NextCombatTime { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int NameHue { get; set; } = -1; [CommandProperty(AccessLevel.GameMaster)] public int Hunger { get => m_Hunger; set { var oldValue = m_Hunger; if (oldValue != value) { m_Hunger = value; EventSink.InvokeHungerChanged(this, oldValue); } } } [CommandProperty(AccessLevel.GameMaster)] public int Thirst { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int BAC { get; set; } /// /// Gets or sets the number of steps this player may take when hidden before being revealed. /// [CommandProperty(AccessLevel.GameMaster)] public int AllowedStealthSteps { get; set; } public Item Holding { get => m_Holding; set { if (m_Holding != value) { if (m_Holding != null) { UpdateTotal(m_Holding, TotalType.Weight, -(m_Holding.TotalWeight + m_Holding.PileWeight)); if (m_Holding.HeldBy == this) m_Holding.HeldBy = null; } if (value != null && m_Holding != null) DropHolding(); m_Holding = value; if (m_Holding != null) { UpdateTotal(m_Holding, TotalType.Weight, m_Holding.TotalWeight + m_Holding.PileWeight); m_Holding.HeldBy ??= this; } } } } public long LastMoveTime { get; set; } [CommandProperty(AccessLevel.GameMaster)] public virtual bool Paralyzed { get => m_Paralyzed; set { if (m_Paralyzed != value) { m_Paralyzed = value; Delta(MobileDelta.Flags); SendLocalizedMessage(m_Paralyzed ? 502381 : 502382); if (m_ParaTimer != null) { m_ParaTimer.Stop(); m_ParaTimer = null; } } } } [CommandProperty(AccessLevel.GameMaster)] public bool DisarmReady { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool StunReady { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool Frozen { get => m_Frozen; set { if (m_Frozen != value) { m_Frozen = value; Delta(MobileDelta.Flags); if (m_FrozenTimer != null) { m_FrozenTimer.Stop(); m_FrozenTimer = null; } } } } /// /// Gets or sets the lock state for the property. /// [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public StatLockType StrLock { get => m_StrLock; set { if (m_StrLock != value) { m_StrLock = value; m_NetState?.Send(new StatLockInfo(this)); } } } /// /// Gets or sets the lock state for the property. /// [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public StatLockType DexLock { get => m_DexLock; set { if (m_DexLock != value) { m_DexLock = value; m_NetState?.Send(new StatLockInfo(this)); } } } /// /// Gets or sets the lock state for the property. /// [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public StatLockType IntLock { get => m_IntLock; set { if (m_IntLock != value) { m_IntLock = value; m_NetState?.Send(new StatLockInfo(this)); } } } public long NextActionTime { get; set; } public long NextActionMessage { get; set; } public static int ActionMessageDelay { get; set; } = 125; public static bool GlobalRegenThroughPoison { get; set; } = true; public virtual bool RegenThroughPoison => GlobalRegenThroughPoison; public virtual bool CanRegenHits => Alive && (RegenThroughPoison || !Poisoned); public virtual bool CanRegenStam => Alive; public virtual bool CanRegenMana => Alive; public long NextSkillTime { get; set; } public List Aggressors { get; private set; } public List Aggressed { get; private set; } public bool ChangingCombatant => m_ChangingCombatant > 0; /// /// Overridable. Gets or sets which Mobile that this Mobile is currently engaged in combat with. /// /// [CommandProperty(AccessLevel.GameMaster)] public virtual Mobile Combatant { get => m_Combatant; set { if (Deleted) return; if (m_Combatant != value && value != this) { var old = m_Combatant; ++m_ChangingCombatant; m_Combatant = value; if (m_Combatant != null && !CanBeHarmful(m_Combatant, false) || !Region.OnCombatantChange(this, old, m_Combatant)) { m_Combatant = old; --m_ChangingCombatant; return; } m_NetState?.Send(new ChangeCombatant(m_Combatant)); if (m_Combatant == null) { m_ExpireCombatant?.Stop(); m_CombatTimer?.Stop(); m_ExpireCombatant = null; m_CombatTimer = null; } else { m_ExpireCombatant ??= new ExpireCombatantTimer(this); m_ExpireCombatant.Start(); m_CombatTimer ??= new CombatTimer(this); m_CombatTimer.Start(); } if (m_Combatant != null && CanBeHarmful(m_Combatant, false)) { DoHarmful(m_Combatant); m_Combatant?.PlaySound(m_Combatant.GetAngerSound()); } OnCombatantChange(); --m_ChangingCombatant; } } } [CommandProperty(AccessLevel.GameMaster)] public int TotalGold => GetTotal(TotalType.Gold); [CommandProperty(AccessLevel.GameMaster)] public int TotalItems => GetTotal(TotalType.Items); [CommandProperty(AccessLevel.GameMaster)] public int TotalWeight => GetTotal(TotalType.Weight); [CommandProperty(AccessLevel.GameMaster)] public int TithingPoints { get => m_TithingPoints; set { if (m_TithingPoints != value) { m_TithingPoints = value; Delta(MobileDelta.TithingPoints); } } } [CommandProperty(AccessLevel.GameMaster)] public int Followers { get => m_Followers; set { if (m_Followers != value) { m_Followers = value; Delta(MobileDelta.Followers); } } } [CommandProperty(AccessLevel.GameMaster)] public int FollowersMax { get => m_FollowersMax; set { if (m_FollowersMax != value) { m_FollowersMax = value; Delta(MobileDelta.Followers); } } } public bool TargetLocked { get; set; } public Target Target { get => m_Target; set { var oldTarget = m_Target; var newTarget = value; if (oldTarget == newTarget) return; m_Target = null; if (oldTarget != null && newTarget != null) oldTarget.Cancel(this, TargetCancelType.Overridden); m_Target = newTarget; if (newTarget != null && m_NetState != null && !TargetLocked) m_NetState.Send(newTarget.GetPacketFor(m_NetState)); OnTargetChange(); } } public ContextMenu ContextMenu { get => m_ContextMenu; set { m_ContextMenu = value; if (m_ContextMenu != null && m_NetState != null) { // Old packet is preferred until assistants catch up if (m_NetState.NewHaven && m_ContextMenu.RequiresNewPacket) Send(new DisplayContextMenu(m_ContextMenu)); else Send(new DisplayContextMenuOld(m_ContextMenu)); } } } public bool Pushing { get; set; } public static int WalkFoot { get; set; } = 400; public static int RunFoot { get; set; } = 200; public static int WalkMount { get; set; } = 200; public static int RunMount { get; set; } = 100; public static AccessLevel FwdAccessOverride { get; set; } = AccessLevel.Counselor; public static bool FwdEnabled { get; set; } = true; public static bool FwdUOTDOverride { get; set; } = false; public static int FwdMaxSteps { get; set; } = 4; public virtual bool IsDeadBondedPet => false; public ISpell Spell { get => m_Spell; set { if (m_Spell != null && value != null) Console.WriteLine("Warning: Spell has been overwritten"); m_Spell = value; } } [CommandProperty(AccessLevel.Administrator)] public bool AutoPageNotify { get; set; } [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] public IAccount Account { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int VirtualArmor { get => m_VirtualArmor; set { if (m_VirtualArmor != value) { m_VirtualArmor = value; Delta(MobileDelta.Armor); } } } [CommandProperty(AccessLevel.GameMaster)] public virtual double ArmorRating => 0.0; /// /// Overridable. Returns true if the player is alive, false if otherwise. By default, this is computed by: /// !Deleted && (!Player || !Body.IsGhost) /// [CommandProperty(AccessLevel.Counselor)] public virtual bool Alive => !Deleted && (!m_Player || !m_Body.IsGhost); public static CreateCorpseHandler CreateCorpseHandler { get; set; } public virtual bool RetainPackLocsOnDeath => Core.AOS; [CommandProperty(AccessLevel.GameMaster)] public Container Corpse { get; set; } public static char[] GhostChars { get; set; } = { 'o', 'O' }; public static bool NoSpeechLOS { get; set; } public static TimeSpan AutoManifestTimeout { get; set; } = TimeSpan.FromSeconds(5.0); public static bool InsuranceEnabled { get; set; } public static int ActionDelay { get; set; } = 500; public static VisibleDamageType VisibleDamageType { get; set; } public List DamageEntries { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public Mobile LastKiller { get; set; } public static bool DefaultShowVisibleDamage { get; set; } public static bool DefaultCanSeeVisibleDamage { get; set; } public virtual bool ShowVisibleDamage => DefaultShowVisibleDamage; public virtual bool CanSeeVisibleDamage => DefaultCanSeeVisibleDamage; [CommandProperty(AccessLevel.GameMaster)] public bool Squelched { get; set; } public virtual bool ShouldCheckStatTimers => true; [CommandProperty(AccessLevel.GameMaster)] public DateTime CreationTime { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public int LightLevel { get => m_LightLevel; set { if (m_LightLevel != value) { m_LightLevel = value; CheckLightLevels(false); /*if (m_NetState != null) m_NetState.Send( new PersonalLightLevel( this ) );*/ } } } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public string Profile { get; set; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public bool ProfileLocked { get; set; } [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] public bool Player { get => m_Player; set { m_Player = value; InvalidateProperties(); if (!m_Player && m_Dex <= 100 && m_CombatTimer != null) m_CombatTimer.Priority = TimerPriority.FiftyMS; else if (m_CombatTimer != null) m_CombatTimer.Priority = TimerPriority.EveryTick; CheckStatTimers(); } } [CommandProperty(AccessLevel.GameMaster)] public string Title { get => m_Title; set { m_Title = value; InvalidateProperties(); } } public List Items { get; private set; } public virtual int MaxWeight => int.MaxValue; public static IWeapon DefaultWeapon { get; set; } [CommandProperty(AccessLevel.Counselor)] public Skills Skills { get; private set; } [CommandProperty(AccessLevel.Counselor, AccessLevel.Administrator)] public AccessLevel AccessLevel { get => m_AccessLevel; set { var oldValue = m_AccessLevel; if (oldValue != value) { m_AccessLevel = value; Delta(MobileDelta.Noto); InvalidateProperties(); SendMessage("Your access level has been changed. You are now {0}.", GetAccessLevelName(value)); ClearScreen(); SendEverything(); OnAccessLevelChanged(oldValue); } } } [CommandProperty(AccessLevel.GameMaster)] public int Fame { get => m_Fame; set { var oldValue = m_Fame; if (oldValue != value) { m_Fame = value; if (ShowFameTitle && (m_Player || m_Body.IsHuman) && oldValue >= 10000 != value >= 10000) InvalidateProperties(); OnFameChange(oldValue); } } } [CommandProperty(AccessLevel.GameMaster)] public int Karma { get => m_Karma; set { var old = m_Karma; if (old != value) { m_Karma = value; OnKarmaChange(old); } } } [CommandProperty(AccessLevel.GameMaster)] public bool Blessed { get => m_Blessed; set { if (m_Blessed != value) { m_Blessed = value; Delta(MobileDelta.HealthbarYellow); } } } public virtual int Luck => 0; [Hue] [CommandProperty(AccessLevel.GameMaster)] public int HueMod { get => m_HueMod; set { if (m_HueMod != value) { m_HueMod = value; Delta(MobileDelta.Hue); } } } [Hue] [CommandProperty(AccessLevel.GameMaster)] public virtual int Hue { get { if (m_HueMod != -1) return m_HueMod; return m_Hue; } set { var oldHue = m_Hue; if (oldHue != value) { m_Hue = value; Delta(MobileDelta.Hue); } } } [CommandProperty(AccessLevel.GameMaster)] public Direction Direction { get => m_Direction; set { if (m_Direction != value) { m_Direction = value; Delta(MobileDelta.Direction); // ProcessDelta(); } } } [CommandProperty(AccessLevel.GameMaster)] public bool Female { get => m_Female; set { if (m_Female != value) { m_Female = value; Delta(MobileDelta.Flags); OnGenderChanged(!m_Female); } } } [CommandProperty(AccessLevel.GameMaster)] public bool Flying { get => m_Flying; set { if (m_Flying != value) { m_Flying = value; Delta(MobileDelta.Flags); } } } [CommandProperty(AccessLevel.GameMaster)] public bool Warmode { get => m_Warmode; set { if (Deleted) return; if (m_Warmode != value) { if (m_AutoManifestTimer != null) { m_AutoManifestTimer.Stop(); m_AutoManifestTimer = null; } m_Warmode = value; Delta(MobileDelta.Flags); if (m_NetState != null) Send(SetWarMode.Instantiate(value)); if (!m_Warmode) Combatant = null; if (!Alive) { if (value) Delta(MobileDelta.GhostUpdate); else SendRemovePacket(false); } OnWarmodeChanged(); } } } [CommandProperty(AccessLevel.GameMaster)] public bool Hidden { get => m_Hidden; set { if (m_Hidden != value) { m_Hidden = value; // Delta( MobileDelta.Flags ); OnHiddenChanged(); } } } [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] public NetState NetState { get => m_NetState?.Connection != null && !m_NetState.IsDisposing ? m_NetState : null; set { if (m_NetState != value) { m_Map?.OnClientChange(m_NetState, value, this); m_Target?.Cancel(this, TargetCancelType.Disconnected); QuestArrow = null; m_Spell?.OnConnectionChanged(); // if (m_Spell != null) // m_Spell.FinishSequence(); m_NetState?.CancelAllTrades(); var box = FindBankNoCreate(); if (box?.Opened == true) box.Close(); // REMOVED: // m_Actions.Clear(); m_NetState = value; if (m_NetState == null) { OnDisconnected(); EventSink.InvokeDisconnected(this); // Disconnected, start the logout timer if (m_LogoutTimer == null) m_LogoutTimer = new LogoutTimer(this); else m_LogoutTimer.Stop(); m_LogoutTimer.Delay = GetLogoutDelay(); m_LogoutTimer.Start(); } else { OnConnected(); EventSink.InvokeConnected(this); // Connected, stop the logout timer and if needed, move to the world m_LogoutTimer?.Stop(); m_LogoutTimer = null; if (m_Map == Map.Internal && LogoutMap != null) { Map = LogoutMap; Location = LogoutLocation; } } for (var i = Items.Count - 1; i >= 0; --i) { if (i >= Items.Count) continue; var item = Items[i]; if (item is SecureTradeContainer) { for (var j = item.Items.Count - 1; j >= 0; --j) if (j < item.Items.Count) { item.Items[j].OnSecureTrade(this, this, this, false); AddToBackpack(item.Items[j]); } Timer.DelayCall(TimeSpan.Zero, item.Delete); } } DropHolding(); OnNetStateChanged(); } } } [CommandProperty(AccessLevel.GameMaster)] public string Language { get => m_Language; set => m_Language = value; } [CommandProperty(AccessLevel.GameMaster)] public int SpeechHue { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int EmoteHue { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int WhisperHue { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int YellHue { get; set; } [CommandProperty(AccessLevel.GameMaster)] public string GuildTitle { get => m_GuildTitle; set { var old = m_GuildTitle; if (old != value) { m_GuildTitle = value; if (m_Guild?.Disbanded == false && m_GuildTitle != null) SendLocalizedMessage(1018026, true, m_GuildTitle); // Your guild title has changed : InvalidateProperties(); OnGuildTitleChange(old); } } } [CommandProperty(AccessLevel.GameMaster)] public bool DisplayGuildTitle { get => m_DisplayGuildTitle; set { m_DisplayGuildTitle = value; InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public Mobile GuildFealty { get; set; } [CommandProperty(AccessLevel.GameMaster)] public string NameMod { get => m_NameMod; set { if (m_NameMod != value) { m_NameMod = value; Delta(MobileDelta.Name); InvalidateProperties(); } } } [CommandProperty(AccessLevel.GameMaster)] public bool YellowHealthbar { get => m_YellowHealthbar; set { m_YellowHealthbar = value; Delta(MobileDelta.HealthbarYellow); } } [CommandProperty(AccessLevel.GameMaster)] public string RawName { get => m_Name; set => Name = value; } [CommandProperty(AccessLevel.GameMaster)] public virtual string Name { get => m_NameMod ?? m_Name; set { if (m_Name != value) // I'm leaving out the && m_NameMod == null { var oldName = m_Name; m_Name = value; OnAfterNameChange(oldName, m_Name); Delta(MobileDelta.Name); InvalidateProperties(); } } } [CommandProperty(AccessLevel.GameMaster)] public DateTime LastStrGain { get; set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime LastIntGain { get; set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime LastDexGain { get; set; } public DateTime LastStatGain { get { var d = LastStrGain; if (LastIntGain > d) d = LastIntGain; if (LastDexGain > d) d = LastDexGain; return d; } set { LastStrGain = value; LastIntGain = value; LastDexGain = value; } } public BaseGuild Guild { get => m_Guild; set { var old = m_Guild; if (old != value) { if (value == null) GuildTitle = null; m_Guild = value; Delta(MobileDelta.Noto); InvalidateProperties(); OnGuildChange(old); } } } public Region WalkRegion { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool Poisoned => m_Poison != null; [CommandProperty(AccessLevel.GameMaster)] public bool IsBodyMod => m_BodyMod.BodyID != 0; [CommandProperty(AccessLevel.GameMaster)] public Body BodyMod { get => m_BodyMod; set { if (m_BodyMod != value) { m_BodyMod = value; Delta(MobileDelta.Body); InvalidateProperties(); CheckStatTimers(); } } } [Body] [CommandProperty(AccessLevel.GameMaster)] public Body Body { get { if (IsBodyMod) return m_BodyMod; return m_Body; } set { if (m_Body != value && !IsBodyMod) { m_Body = SafeBody(value); Delta(MobileDelta.Body); InvalidateProperties(); CheckStatTimers(); } } } [Body] [CommandProperty(AccessLevel.GameMaster)] public int BodyValue { get => Body.BodyID; set => Body = value; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public Point3D LogoutLocation { get; set; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public Map LogoutMap { get; set; } public Region Region => m_Region ?? (Map == null ? Map.Internal.DefaultRegion : Map.DefaultRegion); public Packet RemovePacket => StaticPacketHandlers.GetRemoveEntityPacket(this); public OPLInfo OPLPacket => StaticPacketHandlers.GetOPLInfoPacket(this); private ObjectPropertyList m_PropertyList; public ObjectPropertyList PropertyList => m_PropertyList ??= NewObjectPropertyList(); public void ReleaseOPLPacket() { if (m_PropertyList == null) return; Packet.Release(m_PropertyList); m_PropertyList = null; } [CommandProperty(AccessLevel.GameMaster)] public int SolidHueOverride { get => m_SolidHueOverride; set { if (m_SolidHueOverride == value) return; m_SolidHueOverride = value; Delta(MobileDelta.Hue | MobileDelta.Body); } } [CommandProperty(AccessLevel.GameMaster)] public virtual IWeapon Weapon { get { if (m_Weapon is Item item && !item.Deleted && item.Parent == this && CanSee(item)) return m_Weapon; m_Weapon = null; item = FindItemOnLayer(Layer.OneHanded) ?? FindItemOnLayer(Layer.TwoHanded); if (item is IWeapon weapon) return m_Weapon = weapon; return GetDefaultWeapon(); } } [CommandProperty(AccessLevel.GameMaster)] public BankBox BankBox { get { if (m_BankBox?.Deleted == false && m_BankBox.Parent == this) return m_BankBox; m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; if (m_BankBox == null) AddItem(m_BankBox = new BankBox(this)); return m_BankBox; } } [CommandProperty(AccessLevel.GameMaster)] public Container Backpack { get { if (m_Backpack?.Deleted != false || m_Backpack.Parent != this) m_Backpack = FindItemOnLayer(Layer.Backpack) as Container; return m_Backpack; } } public virtual bool KeepsItemsOnDeath => m_AccessLevel > AccessLevel.Player; public bool HasTrade => m_NetState?.Trades.Count > 0; public bool NoMoveHS { get; set; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public int Kills { get => m_Kills; set { var oldValue = m_Kills; if (m_Kills != value) { m_Kills = value; if (m_Kills < 0) m_Kills = 0; if (oldValue >= 5 != m_Kills >= 5) { Delta(MobileDelta.Noto); InvalidateProperties(); } OnKillsChange(oldValue); } } } [CommandProperty(AccessLevel.GameMaster)] public int ShortTermMurders { get => m_ShortTermMurders; set { if (m_ShortTermMurders != value) { m_ShortTermMurders = value; if (m_ShortTermMurders < 0) m_ShortTermMurders = 0; } } } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public bool Criminal { get => m_Criminal; set { if (m_Criminal != value) { m_Criminal = value; Delta(MobileDelta.Noto); InvalidateProperties(); } if (m_Criminal) { if (m_ExpireCriminal == null) m_ExpireCriminal = new ExpireCriminalTimer(this); else m_ExpireCriminal.Stop(); m_ExpireCriminal.Start(); } else if (m_ExpireCriminal != null) { m_ExpireCriminal.Stop(); m_ExpireCriminal = null; } } } public static bool DisableDismountInWarmode { get; set; } public static int BodyWeight { get; set; } = 14; [CommandProperty(AccessLevel.GameMaster)] public IMount Mount { get { Item item = null; if (m_MountItem?.Deleted == false && m_MountItem.Parent == this) item = m_MountItem; item ??= FindItemOnLayer(Layer.Mount); if (!(item is IMountItem mountItem)) return null; m_MountItem = item; return mountItem.Mount; } } [CommandProperty(AccessLevel.GameMaster)] public bool Mounted => Mount != null; public QuestArrow QuestArrow { get => m_QuestArrow; set { if (m_QuestArrow != value) { m_QuestArrow?.Stop(); m_QuestArrow = value; } } } public virtual bool CanTarget => true; public virtual bool ClickTitle => true; public virtual bool PropertyTitle => OldPropertyTitles ? ClickTitle : true; public static bool DisableHiddenSelfClick { get; set; } = true; public static bool AsciiClickMessage { get; set; } = true; public static bool GuildClickMessage { get; set; } = true; public static bool OldPropertyTitles { get; set; } public virtual bool ShowFameTitle // (m_Player || m_Body.IsHuman) && m_Fame >= 10000; } => true; /// /// Gets or sets the maximum attainable value for , , and . /// [CommandProperty(AccessLevel.GameMaster)] public int StatCap { get => m_StatCap; set { if (m_StatCap != value) { m_StatCap = value; Delta(MobileDelta.StatCap); } } } [CommandProperty(AccessLevel.GameMaster)] public bool Meditating { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool CanSwim { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool CantWalk { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool CanHearGhosts { get => m_CanHearGhosts || AccessLevel >= AccessLevel.Counselor; set => m_CanHearGhosts = value; } [CommandProperty(AccessLevel.GameMaster)] public int RawStatTotal => RawStr + RawDex + RawInt; public long NextSpellTime { get; set; } public bool Deleted { get; private set; } public virtual void Delete() { if (Deleted) return; if (!World.OnDelete(this)) return; if (m_NetState != null) { m_NetState.CancelAllTrades(); m_NetState.Dispose(); } DropHolding(); Region.OnRegionChange(this, m_Region, null); m_Region = null; // Is the above line REALLY needed? The old Region system did NOT have said line // and worked fine, because of this a LOT of extra checks have to be done everywhere... // I guess this should be there for Garbage collection purposes, but, still, is it /really/ needed? OnDelete(); for (var i = Items.Count - 1; i >= 0; --i) if (i < Items.Count) Items[i].OnParentDeleted(this); for (var i = 0; i < Stabled.Count; i++) Stabled[i].Delete(); SendRemovePacket(); m_Guild?.OnDelete(this); Deleted = true; m_Map?.OnLeave(this); m_Map = null; m_Hair = null; m_FacialHair = null; m_MountItem = null; World.RemoveMobile(this); OnAfterDelete(); FreeCache(); } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public Map Map { get => m_Map; set { if (Deleted) return; if (m_Map != value) { m_NetState?.ValidateAllTrades(); var oldMap = m_Map; if (m_Map != null) { m_Map.OnLeave(this); ClearScreen(); SendRemovePacket(); } for (var i = 0; i < Items.Count; ++i) Items[i].Map = value; m_Map = value; UpdateRegion(); m_Map?.OnEnter(this); var ns = m_NetState; if (ns != null && m_Map != null) { ns.Sequence = 0; ns.Send(new MapChange(this)); if (!Core.SE && ns.ProtocolChanges < ProtocolChanges.Version6000) ns.Send(new MapPatches()); ns.Send(SeasonChange.Instantiate(GetSeason(), true)); if (ns.StygianAbyss) ns.Send(new MobileUpdate(this)); else ns.Send(new MobileUpdateOld(this)); ClearFastwalkStack(); } if (ns != null) { if (m_Map != null) ns.Send(new ServerChange(this, m_Map)); ns.Sequence = 0; ClearFastwalkStack(); ns.Send(MobileIncoming.Create(ns, this, this)); if (ns.StygianAbyss) { ns.Send(new MobileUpdate(this)); CheckLightLevels(true); ns.Send(new MobileUpdate(this)); } else { ns.Send(new MobileUpdateOld(this)); CheckLightLevels(true); ns.Send(new MobileUpdateOld(this)); } } SendEverything(); SendIncomingPacket(); if (ns != null) { ns.Sequence = 0; ClearFastwalkStack(); ns.Send(MobileIncoming.Create(ns, this, this)); if (ns.StygianAbyss) { ns.Send(SupportedFeatures.Instantiate(ns)); ns.Send(new MobileUpdate(this)); ns.Send(new MobileAttributes(this)); } else { ns.Send(SupportedFeatures.Instantiate(ns)); ns.Send(new MobileUpdateOld(this)); ns.Send(new MobileAttributes(this)); } } OnMapChange(oldMap); } } } [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public Point3D Location { get => m_Location; set => SetLocation(value, true); } public virtual void MoveToWorld(Point3D newLocation, Map map) { if (Deleted) return; if (m_Map == map) { SetLocation(newLocation, true); return; } var box = FindBankNoCreate(); if (box?.Opened == true) box.Close(); var oldLocation = m_Location; var oldMap = m_Map; var oldRegion = m_Region; if (oldMap != null) { oldMap.OnLeave(this); ClearScreen(); SendRemovePacket(); } for (var i = 0; i < Items.Count; ++i) Items[i].Map = map; m_Map = map; m_Location = newLocation; var ns = m_NetState; if (m_Map != null) { m_Map.OnEnter(this); UpdateRegion(); if (ns != null && m_Map != null) { ns.Sequence = 0; ns.Send(new MapChange(this)); if (!Core.SE && ns.ProtocolChanges < ProtocolChanges.Version6000) ns.Send(new MapPatches()); ns.Send(SeasonChange.Instantiate(GetSeason(), true)); if (ns.StygianAbyss) ns.Send(new MobileUpdate(this)); else ns.Send(new MobileUpdateOld(this)); ClearFastwalkStack(); } } else { UpdateRegion(); } if (ns != null) { if (m_Map != null) Send(new ServerChange(this, m_Map)); ns.Sequence = 0; ClearFastwalkStack(); ns.Send(MobileIncoming.Create(ns, this, this)); if (ns.StygianAbyss) { ns.Send(new MobileUpdate(this)); CheckLightLevels(true); ns.Send(new MobileUpdate(this)); } else { ns.Send(new MobileUpdateOld(this)); CheckLightLevels(true); ns.Send(new MobileUpdateOld(this)); } } SendEverything(); SendIncomingPacket(); if (ns != null) { ns.Sequence = 0; ClearFastwalkStack(); ns.Send(MobileIncoming.Create(ns, this, this)); if (ns.StygianAbyss) { ns.Send(SupportedFeatures.Instantiate(ns)); ns.Send(new MobileUpdate(this)); ns.Send(new MobileAttributes(this)); } else { ns.Send(SupportedFeatures.Instantiate(ns)); ns.Send(new MobileUpdateOld(this)); ns.Send(new MobileAttributes(this)); } } OnMapChange(oldMap); OnLocationChange(oldLocation); m_Region?.OnLocationChanged(this, oldLocation); } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public int X { get => m_Location.m_X; set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public int Y { get => m_Location.m_Y; set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] public int Z { get => m_Location.m_Z; set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); } public virtual void ProcessDelta() { var m = this; var delta = m.m_DeltaFlags; if (delta == MobileDelta.None) return; var attrs = delta & MobileDelta.Attributes; m.m_DeltaFlags = MobileDelta.None; m.m_InDeltaQueue = false; bool sendHits = false, sendStam = false, sendMana = false, sendAll = false, sendAny = false; bool sendIncoming = false, sendNonlocalIncoming = false; bool sendUpdate = false, sendRemove = false; bool sendPublicStats = false, sendPrivateStats = false; bool sendMoving = false, sendNonlocalMoving = false; var sendOPLUpdate = ObjectPropertyList.Enabled && (delta & MobileDelta.Properties) != 0; bool sendHair = false, sendFacialHair = false, removeHair = false, removeFacialHair = false; bool sendHealthbarPoison = false, sendHealthbarYellow = false; if (attrs != MobileDelta.None) { sendAny = true; if (attrs == MobileDelta.Attributes) { sendAll = true; } else { sendHits = (attrs & MobileDelta.Hits) != 0; sendStam = (attrs & MobileDelta.Stam) != 0; sendMana = (attrs & MobileDelta.Mana) != 0; } } if ((delta & MobileDelta.GhostUpdate) != 0) sendNonlocalIncoming = true; if ((delta & MobileDelta.Hue) != 0) { sendNonlocalIncoming = true; sendUpdate = true; sendRemove = true; } if ((delta & MobileDelta.Direction) != 0) { sendNonlocalMoving = true; sendUpdate = true; } if ((delta & MobileDelta.Body) != 0) { sendUpdate = true; sendIncoming = true; } /*if ((delta & MobileDelta.Hue) != 0) { sendNonlocalIncoming = true; sendUpdate = true; } else if ((delta & (MobileDelta.Direction | MobileDelta.Body)) != 0) { sendNonlocalMoving = true; sendUpdate = true; } else*/ if ((delta & (MobileDelta.Flags | MobileDelta.Noto)) != 0) sendMoving = true; if ((delta & MobileDelta.HealthbarPoison) != 0) sendHealthbarPoison = true; if ((delta & MobileDelta.HealthbarYellow) != 0) sendHealthbarYellow = true; if ((delta & MobileDelta.Name) != 0) { sendAll = false; sendHits = false; sendAny = sendStam || sendMana; sendPublicStats = true; } if ((delta & (MobileDelta.WeaponDamage | MobileDelta.Resistances | MobileDelta.Stat | MobileDelta.Weight | MobileDelta.Gold | MobileDelta.Armor | MobileDelta.StatCap | MobileDelta.Followers | MobileDelta.TithingPoints | MobileDelta.Race)) != 0) sendPrivateStats = true; if ((delta & MobileDelta.Hair) != 0) { if (m.HairItemID <= 0) removeHair = true; sendHair = true; } if ((delta & MobileDelta.FacialHair) != 0) { if (m.FacialHairItemID <= 0) removeFacialHair = true; sendFacialHair = true; } var cache = new[] { new Packet[8], new Packet[8] }; var ourState = m.m_NetState; if (ourState != null) { if (sendUpdate) { ourState.Sequence = 0; if (ourState.StygianAbyss) ourState.Send(new MobileUpdate(m)); else ourState.Send(new MobileUpdateOld(m)); ClearFastwalkStack(); } if (sendIncoming) ourState.Send(MobileIncoming.Create(ourState, m, m)); if (ourState.StygianAbyss) { if (sendMoving) { var noto = Notoriety.Compute(m, m); ourState.Send(cache[0][noto] = Packet.Acquire(new MobileMoving(m, noto))); } if (sendHealthbarPoison) ourState.Send(new HealthbarPoison(m)); if (sendHealthbarYellow) ourState.Send(new HealthbarYellow(m)); } else { if (sendMoving || sendHealthbarPoison || sendHealthbarYellow) { var noto = Notoriety.Compute(m, m); ourState.Send(cache[1][noto] = Packet.Acquire(new MobileMovingOld(m, noto))); } } if (sendPublicStats || sendPrivateStats) { ourState.Send(new MobileStatusExtended(m, m_NetState)); } else if (sendAll) { ourState.Send(new MobileAttributes(m)); } else if (sendAny) { if (sendHits) ourState.Send(new MobileHits(m)); if (sendStam) ourState.Send(new MobileStam(m)); if (sendMana) ourState.Send(new MobileMana(m)); } if (sendStam || sendMana) if (Party is IParty ip) { if (sendStam) ip.OnStamChanged(this); if (sendMana) ip.OnManaChanged(this); } if (sendHair) { if (removeHair) ourState.Send(new RemoveHair(m)); else ourState.Send(new HairEquipUpdate(m)); } if (sendFacialHair) { if (removeFacialHair) ourState.Send(new RemoveFacialHair(m)); else ourState.Send(new FacialHairEquipUpdate(m)); } if (sendOPLUpdate) ourState.Send(OPLPacket); } sendMoving = sendMoving || sendNonlocalMoving; sendIncoming = sendIncoming || sendNonlocalIncoming; sendHits = sendHits || sendAll; if (m.m_Map != null && (sendRemove || sendIncoming || sendPublicStats || sendHits || sendMoving || sendOPLUpdate || sendHair || sendFacialHair || sendHealthbarPoison || sendHealthbarYellow)) { Mobile beholder; Packet hitsPacket = null; Packet statPacketTrue = null; Packet statPacketFalse = null; Packet deadPacket = null; Packet hairPacket = null; Packet facialhairPacket = null; Packet hbpPacket = null; Packet hbyPacket = null; var eable = m.Map.GetClientsInRange(m.m_Location); foreach (var state in eable) { beholder = state.Mobile; if (beholder != m && beholder.CanSee(m)) { if (sendRemove) state.Send(RemovePacket); if (sendIncoming) { state.Send(MobileIncoming.Create(state, beholder, m)); if (m.IsDeadBondedPet) { deadPacket ??= Packet.Acquire(new BondedStatus(m.Serial, true)); state.Send(deadPacket); } } if (state.StygianAbyss) { if (sendMoving) { var noto = Notoriety.Compute(beholder, m); var p = cache[0][noto]; if (p == null) cache[0][noto] = p = Packet.Acquire(new MobileMoving(m, noto)); state.Send(p); } if (sendHealthbarPoison) { hbpPacket ??= Packet.Acquire(new HealthbarPoison(m)); state.Send(hbpPacket); } if (sendHealthbarYellow) { hbyPacket ??= Packet.Acquire(new HealthbarYellow(m)); state.Send(hbyPacket); } } else { if (sendMoving || sendHealthbarPoison || sendHealthbarYellow) { var noto = Notoriety.Compute(beholder, m); var p = cache[1][noto]; if (p == null) cache[1][noto] = p = Packet.Acquire(new MobileMovingOld(m, noto)); state.Send(p); } } if (sendPublicStats) { if (m.CanBeRenamedBy(beholder)) { statPacketTrue ??= Packet.Acquire(new MobileStatusCompact(true, m)); state.Send(statPacketTrue); } else { statPacketFalse ??= Packet.Acquire(new MobileStatusCompact(false, m)); state.Send(statPacketFalse); } } else if (sendHits) { hitsPacket ??= Packet.Acquire(new MobileHitsN(m)); state.Send(hitsPacket); } if (sendHair) { hairPacket ??= removeHair ? Packet.Acquire(new RemoveHair(m)) : Packet.Acquire(new HairEquipUpdate(m)); state.Send(hairPacket); } if (sendFacialHair) { facialhairPacket ??= removeFacialHair ? Packet.Acquire(new RemoveFacialHair(m)) : Packet.Acquire(new FacialHairEquipUpdate(m)); state.Send(facialhairPacket); } if (sendOPLUpdate) state.Send(OPLPacket); } } Packet.Release(hitsPacket); Packet.Release(statPacketTrue); Packet.Release(statPacketFalse); Packet.Release(deadPacket); Packet.Release(hairPacket); Packet.Release(facialhairPacket); Packet.Release(hbpPacket); Packet.Release(hbyPacket); eable.Free(); } if (sendMoving || sendNonlocalMoving || sendHealthbarPoison || sendHealthbarYellow) for (var i = 0; i < cache.Length; ++i) for (var j = 0; j < cache[i].Length; ++j) Packet.Release(ref cache[i][j]); } public virtual int HuedItemID => m_Female ? 0x2107 : 0x2106; public int TypeRef { get; } public void Serialize() { SaveBuffer.Flush(); Serialize(SaveBuffer); } public virtual void Serialize(IGenericWriter writer) { writer.Write(32); // version writer.WriteDeltaTime(LastStrGain); writer.WriteDeltaTime(LastIntGain); writer.WriteDeltaTime(LastDexGain); byte hairflag = 0x00; if (m_Hair != null) hairflag |= 0x01; if (m_FacialHair != null) hairflag |= 0x02; writer.Write(hairflag); if ((hairflag & 0x01) != 0) m_Hair?.Serialize(writer); if ((hairflag & 0x02) != 0) m_FacialHair?.Serialize(writer); writer.Write(Race); writer.Write(m_TithingPoints); writer.Write(Corpse); writer.Write(CreationTime); writer.Write(Stabled, true); writer.Write(CantWalk); VirtueInfo.Serialize(writer, Virtues); writer.Write(Thirst); writer.Write(BAC); writer.Write(m_ShortTermMurders); // writer.Write( m_ShortTermElapse ); // writer.Write( m_LongTermElapse ); // writer.Write( m_Followers ); writer.Write(m_FollowersMax); writer.Write(MagicDamageAbsorb); writer.Write(GuildFealty); writer.Write(m_Guild); writer.Write(m_DisplayGuildTitle); writer.Write(CanSwim); writer.Write(Squelched); writer.Write(m_Holding); writer.Write(m_VirtualArmor); writer.Write(BaseSoundID); writer.Write(DisarmReady); writer.Write(StunReady); // Poison.Serialize( m_Poison, writer ); writer.Write(m_StatCap); writer.Write(NameHue); writer.Write(m_Hunger); writer.Write(m_Location); writer.Write(m_Body); writer.Write(m_Name); writer.Write(m_GuildTitle); writer.Write(m_Criminal); writer.Write(m_Kills); writer.Write(SpeechHue); writer.Write(EmoteHue); writer.Write(WhisperHue); writer.Write(YellHue); writer.Write(m_Language); writer.Write(m_Female); writer.Write(m_Warmode); writer.Write(m_Hidden); writer.Write((byte)m_Direction); writer.Write(m_Hue); writer.Write(m_Str); writer.Write(m_Dex); writer.Write(m_Int); writer.Write(m_Hits); writer.Write(m_Stam); writer.Write(m_Mana); writer.Write(m_Map); writer.Write(m_Blessed); writer.Write(m_Fame); writer.Write(m_Karma); writer.Write((byte)m_AccessLevel); Skills.Serialize(writer); writer.Write(Items); writer.Write(m_Player); writer.Write(m_Title); writer.Write(Profile); writer.Write(ProfileLocked); writer.Write(AutoPageNotify); writer.Write(LogoutLocation); writer.Write(LogoutMap); writer.Write((byte)m_StrLock); writer.Write((byte)m_DexLock); writer.Write((byte)m_IntLock); } public ISpawner Spawner { get; set; } public virtual void OnBeforeSpawn(Point3D location, Map m) { } public virtual void OnAfterSpawn() { } protected virtual void OnRaceChange(Race oldRace) { } public virtual void ComputeLightLevels(out int global, out int personal) { ComputeBaseLightLevels(out global, out personal); m_Region?.AlterLightLevel(this, ref global, ref personal); } public virtual void ComputeBaseLightLevels(out int global, out int personal) { global = 0; personal = m_LightLevel; } public virtual void CheckLightLevels(bool forceResend) { } public virtual void UpdateResistances() { Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; var delta = false; for (var i = 0; i < Resistances.Length; ++i) if (Resistances[i] != int.MinValue) { Resistances[i] = int.MinValue; delta = true; } if (delta) Delta(MobileDelta.Resistances); } public virtual int GetResistance(ResistanceType type) { Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; var v = (int)type; if (v < 0 || v >= Resistances.Length) return 0; var res = Resistances[v]; if (res == int.MinValue) { ComputeResistances(); res = Resistances[v]; } return res; } public virtual void AddResistanceMod(ResistanceMod toAdd) { ResistanceMods ??= new List(); ResistanceMods.Add(toAdd); UpdateResistances(); } public virtual void RemoveResistanceMod(ResistanceMod toRemove) { if (ResistanceMods != null) { ResistanceMods.Remove(toRemove); if (ResistanceMods.Count == 0) ResistanceMods = null; } UpdateResistances(); } public virtual void ComputeResistances() { Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; for (var i = 0; i < Resistances.Length; ++i) Resistances[i] = 0; Resistances[0] += BasePhysicalResistance; Resistances[1] += BaseFireResistance; Resistances[2] += BaseColdResistance; Resistances[3] += BasePoisonResistance; Resistances[4] += BaseEnergyResistance; for (var i = 0; ResistanceMods != null && i < ResistanceMods.Count; ++i) { var mod = ResistanceMods[i]; var v = (int)mod.Type; if (v >= 0 && v < Resistances.Length) Resistances[v] += mod.Offset; } for (var i = 0; i < Items.Count; ++i) { var item = Items[i]; if (item.CheckPropertyConflict(this)) continue; Resistances[0] += item.PhysicalResistance; Resistances[1] += item.FireResistance; Resistances[2] += item.ColdResistance; Resistances[3] += item.PoisonResistance; Resistances[4] += item.EnergyResistance; } for (var i = 0; i < Resistances.Length; ++i) { var min = GetMinResistance((ResistanceType)i); var max = GetMaxResistance((ResistanceType)i); if (max < min) max = min; if (Resistances[i] > max) Resistances[i] = max; else if (Resistances[i] < min) Resistances[i] = min; } } public virtual int GetMinResistance(ResistanceType type) => int.MinValue; public virtual int GetMaxResistance(ResistanceType type) => m_Player ? MaxPlayerResistance : int.MaxValue; public int GetAOSStatus(int index) => AOSStatusHandler?.Invoke(this, index) ?? 0; public virtual void SendPropertiesTo(Mobile from) { from.Send(PropertyList); } public virtual void OnAosSingleClick(Mobile from) { var opl = PropertyList; if (opl.Header > 0) { int hue; if (NameHue != -1) hue = NameHue; else if (m_AccessLevel > AccessLevel.Player) hue = 11; else hue = Notoriety.GetHue(Notoriety.Compute(from, this)); from.Send(new MessageLocalized(Serial, Body, MessageType.Label, hue, 3, opl.Header, Name, opl.HeaderArgs)); } } public virtual string ApplyNameSuffix(string suffix) => suffix; public virtual void AddNameProperties(ObjectPropertyList list) { var name = Name ?? string.Empty; var prefix = ""; if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) prefix = m_Female ? "Lady" : "Lord"; var suffix = ""; if (PropertyTitle && !string.IsNullOrEmpty(Title)) suffix = Title; var guild = m_Guild; if (guild != null && (m_Player || m_DisplayGuildTitle)) suffix = suffix.Length > 0 ? $"{suffix} [{Utility.FixHtml(guild.Abbreviation)}]" : $"[{Utility.FixHtml(guild.Abbreviation)}]"; suffix = ApplyNameSuffix(suffix); list.Add(1050045, "{0} \t{1}\t {2}", prefix, name, suffix); // ~1_PREFIX~~2_NAME~~3_SUFFIX~ if (guild != null && (m_DisplayGuildTitle || (m_Player && guild.Type != GuildType.Regular))) { string type; if (guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length) type = m_GuildTypes[(int)guild.Type]; else type = ""; var title = GuildTitle?.Trim() ?? ""; if (NewGuildDisplay && title.Length > 0) { list.Add("{0}, {1}", Utility.FixHtml(title), Utility.FixHtml(guild.Name)); } else { if (title.Length > 0) list.Add("{0}, {1} Guild{2}", Utility.FixHtml(title), Utility.FixHtml(guild.Name), type); else list.Add(Utility.FixHtml(guild.Name)); } } } public virtual void GetProperties(ObjectPropertyList list) { AddNameProperties(list); } public virtual void GetChildProperties(ObjectPropertyList list, Item item) { } public virtual void GetChildNameProperties(ObjectPropertyList list, Item item) { } private void UpdateAggrExpire() { if (Deleted || (Aggressors.Count == 0 && Aggressed.Count == 0)) { StopAggrExpire(); } else if (m_ExpireAggrTimer == null) { m_ExpireAggrTimer = new ExpireAggressorsTimer(this); m_ExpireAggrTimer.Start(); } } private void StopAggrExpire() { m_ExpireAggrTimer?.Stop(); m_ExpireAggrTimer = null; } private void CheckAggrExpire() { for (var i = Aggressors.Count - 1; i >= 0; --i) { if (i >= Aggressors.Count) continue; var info = Aggressors[i]; if (info.Expired) { var attacker = info.Attacker; attacker.RemoveAggressed(this); Aggressors.RemoveAt(i); info.Free(); if (m_NetState != null && CanSee(attacker) && Utility.InUpdateRange(m_Location, attacker.m_Location)) m_NetState.Send(MobileIncoming.Create(m_NetState, this, attacker)); } } for (var i = Aggressed.Count - 1; i >= 0; --i) { if (i >= Aggressed.Count) continue; var info = Aggressed[i]; if (info.Expired) { var defender = info.Defender; defender.RemoveAggressor(this); Aggressed.RemoveAt(i); info.Free(); if (m_NetState != null && CanSee(defender) && Utility.InUpdateRange(m_Location, defender.m_Location)) m_NetState.Send(MobileIncoming.Create(m_NetState, this, defender)); } } UpdateAggrExpire(); } /// /// Overridable. Virtual event invoked when changes in some way. /// public virtual void OnSkillInvalidated(Skill skill) { } public virtual void UpdateSkillMods() { ValidateSkillMods(); for (var i = 0; i < SkillMods.Count; ++i) { var mod = SkillMods[i]; var sk = Skills[mod.Skill]; sk?.Update(); } } public virtual void ValidateSkillMods() { for (var i = 0; i < SkillMods.Count;) { var mod = SkillMods[i]; if (mod.CheckCondition()) ++i; else InternalRemoveSkillMod(mod); } } public virtual void AddSkillMod(SkillMod mod) { if (mod == null) return; ValidateSkillMods(); if (!SkillMods.Contains(mod)) { SkillMods.Add(mod); mod.Owner = this; var sk = Skills[mod.Skill]; sk?.Update(); } } public virtual void RemoveSkillMod(SkillMod mod) { if (mod == null) return; ValidateSkillMods(); InternalRemoveSkillMod(mod); } private void InternalRemoveSkillMod(SkillMod mod) { if (SkillMods.Contains(mod)) { SkillMods.Remove(mod); mod.Owner = null; var sk = Skills[mod.Skill]; sk?.Update(); } } /// /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Mobile. /// Seemingly no longer functional in newer clients. /// public virtual void OnHelpRequest(Mobile from) { } public void DelayChangeWarmode(bool value) { if (m_WarmodeTimer != null) { m_WarmodeTimer.Value = value; return; } if (m_Warmode == value) return; DateTime now = DateTime.UtcNow, next = m_NextWarmodeChange; if (now > next || m_WarmodeChanges == 0) { m_WarmodeChanges = 1; m_NextWarmodeChange = now + WarmodeSpamCatch; } else if (m_WarmodeChanges == WarmodeCatchCount) { m_WarmodeTimer = new WarmodeTimer(this, value); m_WarmodeTimer.Start(); return; } else { ++m_WarmodeChanges; } Warmode = value; } public bool InLOS(Mobile target) => !Deleted && m_Map != null && (target == this || m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); public bool InLOS(object target) => !Deleted && m_Map != null && (target == this || m_AccessLevel > AccessLevel.Player || (target is Item item && item.RootParent == this) || m_Map.LineOfSight(this, target)); public bool InLOS(Point3D target) => !Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); public bool BeginAction() => BeginAction(typeof(T)); public bool BeginAction(object toLock) { if (_actions == null) { _actions = new List { toLock }; return true; } if (!_actions.Contains(toLock)) { _actions.Add(toLock); return true; } return false; } public bool CanBeginAction() => CanBeginAction(typeof(T)); public bool CanBeginAction(object toLock) => _actions?.Contains(toLock) != true; public void EndAction() => EndAction(typeof(T)); public void EndAction(object toLock) { if (_actions != null) { _actions.Remove(toLock); if (_actions.Count == 0) _actions = null; } } public virtual TimeSpan GetLogoutDelay() => Region.GetLogoutDelay(this); public void Paralyze(TimeSpan duration) { if (!m_Paralyzed) { Paralyzed = true; m_ParaTimer = new ParalyzedTimer(this, duration); m_ParaTimer.Start(); } } public void Freeze(TimeSpan duration) { if (!m_Frozen) { Frozen = true; m_FrozenTimer = new FrozenTimer(this, duration); m_FrozenTimer.Start(); } } public override string ToString() => $"0x{Serial.Value:X} \"{Name}\""; public virtual void SendSkillMessage() { if (NextActionMessage - Core.TickCount >= 0) return; NextActionMessage = Core.TickCount + ActionMessageDelay; SendLocalizedMessage(500118); // You must wait a few moments to use another skill. } public virtual void SendActionMessage() { if (NextActionMessage - Core.TickCount >= 0) return; NextActionMessage = Core.TickCount + ActionMessageDelay; SendLocalizedMessage(500119); // You must wait to perform another action. } public virtual void ClearHands() { ClearHand(FindItemOnLayer(Layer.OneHanded)); ClearHand(FindItemOnLayer(Layer.TwoHanded)); } public virtual void ClearHand(Item item) { if (item?.Movable == true && !item.AllowEquippedCast(this)) { var pack = Backpack; if (pack == null) AddToBackpack(item); else pack.DropItem(item); } } public virtual void Attack(Mobile m) { if (CheckAttack(m)) Combatant = m; } public virtual bool CheckAttack(Mobile m) => Utility.InUpdateRange(this, m) && CanSee(m) && InLOS(m); /// /// Overridable. Virtual event invoked after the property has changed. /// /// public virtual void OnCombatantChange() { } public double GetDistanceToSqrt(Point3D p) { var xDelta = m_Location.m_X - p.m_X; var yDelta = m_Location.m_Y - p.m_Y; return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); } public double GetDistanceToSqrt(Mobile m) { var xDelta = m_Location.m_X - m.m_Location.m_X; var yDelta = m_Location.m_Y - m.m_Location.m_Y; return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); } public double GetDistanceToSqrt(IPoint2D p) { var xDelta = m_Location.m_X - p.X; var yDelta = m_Location.m_Y - p.Y; return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); } public virtual void AggressiveAction(Mobile aggressor) => AggressiveAction(aggressor, false); public virtual void AggressiveAction(Mobile aggressor, bool criminal) { if (aggressor == this) return; var args = AggressiveActionEventArgs.Create(this, aggressor, criminal); EventSink.InvokeAggressiveAction(args); args.Free(); if (Combatant == aggressor) { if (m_ExpireCombatant == null) m_ExpireCombatant = new ExpireCombatantTimer(this); else m_ExpireCombatant.Stop(); m_ExpireCombatant.Start(); } var addAggressor = true; var list = Aggressors; for (var i = 0; i < list.Count; ++i) { var info = list[i]; if (info.Attacker == aggressor) { info.Refresh(); info.CriminalAggression = criminal; info.CanReportMurder = criminal; addAggressor = false; } } list = aggressor.Aggressors; for (var i = 0; i < list.Count; ++i) { var info = list[i]; if (info.Attacker == this) { info.Refresh(); addAggressor = false; } } var addAggressed = true; list = Aggressed; for (var i = 0; i < list.Count; ++i) { var info = list[i]; if (info.Defender == aggressor) { info.Refresh(); addAggressed = false; } } list = aggressor.Aggressed; for (var i = 0; i < list.Count; ++i) { var info = list[i]; if (info.Defender == this) { info.Refresh(); info.CriminalAggression = criminal; info.CanReportMurder = criminal; addAggressed = false; } } var setCombatant = false; if (addAggressor) { Aggressors.Add(AggressorInfo.Create(aggressor, this, criminal)); // new AggressorInfo( aggressor, this, criminal, true ) ); if (CanSee(aggressor)) m_NetState?.Send(MobileIncoming.Create(m_NetState, this, aggressor)); if (Combatant == null) setCombatant = true; UpdateAggrExpire(); } if (addAggressed) { aggressor.Aggressed.Add(AggressorInfo.Create(aggressor, this, criminal)); // new AggressorInfo( aggressor, this, criminal, false ) ); if (CanSee(aggressor)) m_NetState?.Send(MobileIncoming.Create(m_NetState, this, aggressor)); if (Combatant == null) setCombatant = true; UpdateAggrExpire(); } if (setCombatant) Combatant = aggressor; Region.OnAggressed(aggressor, this, criminal); } public void RemoveAggressed(Mobile aggressed) { if (Deleted) return; var list = Aggressed; for (var i = 0; i < list.Count; ++i) { var info = list[i]; if (info.Defender == aggressed) { Aggressed.RemoveAt(i); info.Free(); if (m_NetState != null && CanSee(aggressed)) m_NetState.Send(MobileIncoming.Create(m_NetState, this, aggressed)); break; } } UpdateAggrExpire(); } public void RemoveAggressor(Mobile aggressor) { if (Deleted) return; var list = Aggressors; for (var i = 0; i < list.Count; ++i) { var info = list[i]; if (info.Attacker == aggressor) { Aggressors.RemoveAt(i); info.Free(); if (m_NetState != null && CanSee(aggressor)) m_NetState.Send(MobileIncoming.Create(m_NetState, this, aggressor)); break; } } UpdateAggrExpire(); } public virtual int GetTotal(TotalType type) => type switch { TotalType.Gold => m_TotalGold, TotalType.Items => m_TotalItems, TotalType.Weight => m_TotalWeight, _ => 0 }; public virtual void UpdateTotal(Item sender, TotalType type, int delta) { if (delta == 0 || sender.IsVirtualItem) return; switch (type) { default: m_TotalGold += delta; Delta(MobileDelta.Gold); break; case TotalType.Items: m_TotalItems += delta; break; case TotalType.Weight: m_TotalWeight += delta; Delta(MobileDelta.Weight); OnWeightChange(m_TotalWeight - delta); break; } } public virtual void UpdateTotals() { if (Items == null) return; var oldWeight = m_TotalWeight; m_TotalGold = 0; m_TotalItems = 0; m_TotalWeight = 0; for (var i = 0; i < Items.Count; ++i) { var item = Items[i]; item.UpdateTotals(); if (item.IsVirtualItem) continue; m_TotalGold += item.TotalGold; m_TotalItems += item.TotalItems + 1; m_TotalWeight += item.TotalWeight + item.PileWeight; } if (m_Holding != null) m_TotalWeight += m_Holding.TotalWeight + m_Holding.PileWeight; if (m_TotalWeight != oldWeight) OnWeightChange(oldWeight); } public void ClearQuestArrow() => m_QuestArrow = null; public void ClearTarget() => m_Target = null; public Target BeginTarget(int range, bool allowGround, TargetFlags flags, TargetCallback callback) => Target = new SimpleTarget(range, flags, allowGround, callback); public Target BeginTarget(int range, bool allowGround, TargetFlags flags, TargetStateCallback callback, T state) => Target = new SimpleStateTarget(range, flags, allowGround, callback, state); /// /// Overridable. Virtual event invoked after the Target property has changed. /// protected virtual void OnTargetChange() { } public virtual bool CheckContextMenuDisplay(IEntity target) => true; private bool InternalOnMove(Direction d) { if (!OnMove(d)) return false; var e = MovementEventArgs.Create(this, d); EventSink.InvokeMovement(e); var ret = !e.Blocked; e.Free(); return ret; } /// /// Overridable. Event invoked before the Mobile moves. /// /// True if the move is allowed, false if not. protected virtual bool OnMove(Direction d) { if (m_Hidden && m_AccessLevel == AccessLevel.Player) if (AllowedStealthSteps-- <= 0 || (d & Direction.Running) != 0 || Mounted) RevealingAction(); return true; } public virtual void ClearFastwalkStack() { if (m_MoveRecords != null && m_MoveRecords.Count > 0) m_MoveRecords.Clear(); m_EndQueue = Core.TickCount; } public virtual bool CheckMovement(Direction d, out int newZ) => Movement.Movement.CheckMovement(this, d, out newZ); public virtual bool Move(Direction d) { if (Deleted) return false; var box = FindBankNoCreate(); if (box?.Opened == true) box.Close(); var newLocation = m_Location; var oldLocation = newLocation; if ((m_Direction & Direction.Mask) == (d & Direction.Mask)) { // We are actually moving (not just a direction change) if (m_Spell?.OnCasterMoving(d) == false) return false; if (m_Paralyzed || m_Frozen) { SendLocalizedMessage(500111); // You are frozen and can not move. return false; } if (CheckMovement(d, out var newZ)) { int x = oldLocation.m_X, y = oldLocation.m_Y; int oldX = x, oldY = y; var oldZ = oldLocation.m_Z; switch (d & Direction.Mask) { case Direction.North: --y; break; case Direction.Right: ++x; --y; break; case Direction.East: ++x; break; case Direction.Down: ++x; ++y; break; case Direction.South: ++y; break; case Direction.Left: --x; ++y; break; case Direction.West: --x; break; case Direction.Up: --x; --y; break; } newLocation.m_X = x; newLocation.m_Y = y; newLocation.m_Z = newZ; Pushing = false; var map = m_Map; if (map != null) { var oldSector = map.GetSector(oldX, oldY); var newSector = map.GetSector(x, y); if (oldSector != newSector) { for (var i = 0; i < oldSector.Mobiles.Count; ++i) { var m = oldSector.Mobiles[i]; if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && !m.OnMoveOff(this)) return false; } for (var i = 0; i < oldSector.Items.Count; ++i) { var item = oldSector.Items[i]; if (item.AtWorldPoint(oldX, oldY) && (item.Z == oldZ || (item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z)) && !item.OnMoveOff(this)) return false; } for (var i = 0; i < newSector.Mobiles.Count; ++i) { var m = newSector.Mobiles[i]; if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) return false; } for (var i = 0; i < newSector.Items.Count; ++i) { var item = newSector.Items[i]; if (item.AtWorldPoint(x, y) && (item.Z == newZ || (item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z)) && !item.OnMoveOver(this)) return false; } } else { for (var i = 0; i < oldSector.Mobiles.Count; ++i) { var m = oldSector.Mobiles[i]; if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && !m.OnMoveOff(this)) return false; if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) return false; } for (var i = 0; i < oldSector.Items.Count; ++i) { var item = oldSector.Items[i]; if (item.AtWorldPoint(oldX, oldY) && (item.Z == oldZ || (item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z)) && !item.OnMoveOff(this)) return false; if (item.AtWorldPoint(x, y) && (item.Z == newZ || (item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z)) && !item.OnMoveOver(this)) return false; } } if (!Region.CanMove(this, d, newLocation, oldLocation, m_Map)) return false; } else { return false; } if (!InternalOnMove(d)) return false; if (FwdEnabled && m_NetState != null && m_AccessLevel < FwdAccessOverride && (!FwdUOTDOverride || !m_NetState.IsUOTDClient)) { m_MoveRecords ??= new Queue(6); while (m_MoveRecords.Count > 0) { var r = m_MoveRecords.Peek(); if (r.Expired()) m_MoveRecords.Dequeue(); else break; } if (m_MoveRecords.Count >= FwdMaxSteps) { var fw = new FastWalkEventArgs(m_NetState); EventSink.InvokeFastWalk(fw); if (fw.Blocked) return false; } var delay = ComputeMovementSpeed(d); long end; if (m_MoveRecords.Count > 0) end = m_EndQueue + delay; else end = Core.TickCount + delay; m_MoveRecords.Enqueue(MovementRecord.NewInstance(end)); m_EndQueue = end; } LastMoveTime = Core.TickCount; } else { return false; } DisruptiveAction(); } m_NetState?.Send(MovementAck.Instantiate(m_NetState.Sequence, this)); // new MovementAck( m_NetState.Sequence, this ) ); SetLocation(newLocation, false); SetDirection(d); if (m_Map != null) { var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); foreach (var o in eable) { if (o == this) continue; if (o is Mobile mob) { if (mob.NetState != null) m_MoveClientList.Add(mob); m_MoveList.Add(mob); } else if (o is Item item && item.HandlesOnMovement) { m_MoveList.Add(item); } } eable.Free(); var cache = m_MovingPacketCache; /*for( int i = 0; i < cache.Length; ++i ) for( int j = 0; j < cache[i].Length; ++j ) Packet.Release( ref cache[i][j] );*/ foreach (var m in m_MoveClientList) { var ns = m.NetState; if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) { if (ns.StygianAbyss) { var noto = Notoriety.Compute(m, this); var p = cache[0][noto]; if (p == null) cache[0][noto] = p = Packet.Acquire(new MobileMoving(this, noto)); ns.Send(p); } else { var noto = Notoriety.Compute(m, this); var p = cache[1][noto]; if (p == null) cache[1][noto] = p = Packet.Acquire(new MobileMovingOld(this, noto)); ns.Send(p); } } } for (var i = 0; i < cache.Length; ++i) for (var j = 0; j < cache[i].Length; ++j) Packet.Release(ref cache[i][j]); for (var i = 0; i < m_MoveList.Count; ++i) { var o = m_MoveList[i]; if (o is Mobile mobile) mobile.OnMovement(this, oldLocation); else if (o is Item item) item.OnMovement(this, oldLocation); } if (m_MoveList.Count > 0) m_MoveList.Clear(); if (m_MoveClientList.Count > 0) m_MoveClientList.Clear(); } OnAfterMove(oldLocation); return true; } public virtual void OnAfterMove(Point3D oldLocation) { } public int ComputeMovementSpeed() => ComputeMovementSpeed(Direction, false); public int ComputeMovementSpeed(Direction dir) => ComputeMovementSpeed(dir, true); public virtual int ComputeMovementSpeed(Direction dir, bool checkTurning) { int delay; if (Mounted) delay = (dir & Direction.Running) != 0 ? RunMount : WalkMount; else delay = (dir & Direction.Running) != 0 ? RunFoot : WalkFoot; return delay; } /// /// Overridable. Virtual event invoked when a Mobile moves off this Mobile. /// /// True if the move is allowed, false if not. public virtual bool OnMoveOff(Mobile m) => true; /// /// Overridable. Event invoked when a Mobile moves over this Mobile. /// /// True if the move is allowed, false if not. public virtual bool OnMoveOver(Mobile m) => m_Map == null || Deleted || m.CheckShove(this); public virtual bool CheckShove(Mobile shoved) { if ((m_Map.Rules & MapRules.FreeMovement) == 0) { if (!shoved.Alive || !Alive || shoved.IsDeadBondedPet || IsDeadBondedPet) return true; if (shoved.m_Hidden && shoved.m_AccessLevel > AccessLevel.Player) return true; if (!Pushing) { Pushing = true; int number; if (AccessLevel > AccessLevel.Player) { number = shoved.m_Hidden ? 1019041 : 1019040; } else { if (Stam == StamMax) { number = shoved.m_Hidden ? 1019043 : 1019042; Stam -= 10; RevealingAction(); } else { return false; } } SendLocalizedMessage(number); } } return true; } /// /// Overridable. Virtual event invoked when the Mobile sees another Mobile, , move. /// public virtual void OnMovement(Mobile m, Point3D oldLocation) { } public virtual void CriminalAction(bool message) { if (Deleted) return; Criminal = true; Region.OnCriminalAction(this, message); } public virtual bool IsSnoop(Mobile from) => from != this; /// /// Overridable. Any call to will silently fail if this method returns false. /// /// public virtual bool CheckResurrect() => true; /// /// Overridable. Event invoked before the Mobile is resurrected. /// /// public virtual void OnBeforeResurrect() { } /// /// Overridable. Event invoked after the Mobile is resurrected. /// /// public virtual void OnAfterResurrect() { } public virtual void Resurrect() { if (!Alive) { if (!Region.OnResurrect(this)) return; if (!CheckResurrect()) return; OnBeforeResurrect(); var box = FindBankNoCreate(); if (box?.Opened == true) box.Close(); Poison = null; Warmode = false; Hits = 10; Stam = StamMax; Mana = 0; BodyMod = 0; Body = Race.AliveBody(this); ProcessDeltaQueue(); for (var i = Items.Count - 1; i >= 0; --i) { if (i >= Items.Count) continue; var item = Items[i]; if (item.ItemID == 0x204E) item.Delete(); } SendIncomingPacket(); SendIncomingPacket(); OnAfterResurrect(); // Send( new DeathStatus( false ) ); } } public void DropHolding() { var holding = m_Holding; if (holding != null) { if (!holding.Deleted && holding.HeldBy == this && holding.Map == Map.Internal) AddToBackpack(holding); Holding = null; holding.ClearBounce(); } } /// /// Overridable. Virtual event invoked before the Mobile is deleted. /// public virtual void OnDelete() { Spawner?.Remove(this); Spawner = null; } public virtual bool CheckSpellCast(ISpell spell) => true; /// /// Overridable. Virtual event invoked when the Mobile casts a . /// /// public virtual void OnSpellCast(ISpell spell) { } /// /// Overridable. Virtual event invoked after changes. /// public virtual void OnWeightChange(int oldValue) { } /// /// Overridable. Virtual event invoked when the or property of /// changes. /// public virtual void OnSkillChange(SkillName skill, double oldBase) { } /// /// Overridable. Invoked after the mobile is deleted. When overridden, be sure to call the base method. /// public virtual void OnAfterDelete() { StopAggrExpire(); CheckAggrExpire(); PoisonTimer?.Stop(); m_HitsTimer?.Stop(); m_StamTimer?.Stop(); m_ManaTimer?.Stop(); m_CombatTimer?.Stop(); m_ExpireCombatant?.Stop(); m_LogoutTimer?.Stop(); m_ExpireCriminal?.Stop(); m_WarmodeTimer?.Stop(); m_ParaTimer?.Stop(); m_FrozenTimer?.Stop(); m_AutoManifestTimer?.Stop(); } public virtual bool AllowSkillUse(SkillName name) => true; public virtual bool UseSkill(SkillName name) => Skills.UseSkill(this, name); public virtual bool UseSkill(int skillID) => Skills.UseSkill(this, skillID); public virtual DeathMoveResult GetParentMoveResultFor(Item item) => item.OnParentDeath(this); public virtual DeathMoveResult GetInventoryMoveResultFor(Item item) => item.OnInventoryDeath(this); public virtual void Kill() { if (!CanBeDamaged()) return; if (!Alive || IsDeadBondedPet) return; if (Deleted) return; if (!Region.OnBeforeDeath(this)) return; if (!OnBeforeDeath()) return; var box = FindBankNoCreate(); if (box?.Opened == true) box.Close(); m_NetState?.CancelAllTrades(); m_Spell?.OnCasterKilled(); // m_Spell.Disturb( DisturbType.Kill ); m_Target?.Cancel(this, TargetCancelType.Canceled); DisruptiveAction(); Warmode = false; DropHolding(); Hits = 0; Stam = 0; Mana = 0; Poison = null; Combatant = null; if (Paralyzed) { Paralyzed = false; m_ParaTimer?.Stop(); } if (Frozen) { Frozen = false; m_FrozenTimer?.Stop(); } var content = new List(); var equip = new List(); var moveToPack = new List(); var itemsCopy = new List(Items); var pack = Backpack; for (var i = 0; i < itemsCopy.Count; ++i) { var item = itemsCopy[i]; if (item == pack) continue; var res = GetParentMoveResultFor(item); switch (res) { case DeathMoveResult.MoveToCorpse: { content.Add(item); equip.Add(item); break; } case DeathMoveResult.MoveToBackpack: { moveToPack.Add(item); break; } } } if (pack != null) { var packCopy = new List(pack.Items); for (var i = 0; i < packCopy.Count; ++i) { var item = packCopy[i]; var res = GetInventoryMoveResultFor(item); if (res == DeathMoveResult.MoveToCorpse) content.Add(item); else moveToPack.Add(item); } for (var i = 0; i < moveToPack.Count; ++i) { var item = moveToPack[i]; if (RetainPackLocsOnDeath && item.Parent == pack) continue; pack.DropItem(item); } } HairInfo hair = null; if (m_Hair != null) hair = new HairInfo(m_Hair.ItemID, m_Hair.Hue); FacialHairInfo facialhair = null; if (m_FacialHair != null) facialhair = new FacialHairInfo(m_FacialHair.ItemID, m_FacialHair.Hue); var c = CreateCorpseHandler?.Invoke(this, hair, facialhair, content, equip); /*m_Corpse = c; for ( int i = 0; c != null && i < content.Count; ++i ) c.DropItem( (Item)content[i] ); if (c != null) c.MoveToWorld( this.Location, this.Map );*/ if (m_Map != null) { Packet animPacket = null; var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state != m_NetState) { animPacket ??= Packet.Acquire(new DeathAnimation(this, c)); state.Send(animPacket); if (!state.Mobile.CanSee(this)) state.Send(RemovePacket); } Packet.Release(animPacket); eable.Free(); } Region.OnDeath(this); OnDeath(c); } /// /// Overridable. Event invoked before the Mobile is killed. /// /// /// /// True to continue with death, false to override it. public virtual bool OnBeforeDeath() => true; /// /// Overridable. Event invoked after the Mobile is killed. Primarily, this method is responsible for /// deleting an NPC or turning a PC into a ghost. /// /// /// public virtual void OnDeath(Container c) { var sound = GetDeathSound(); if (sound >= 0) Effects.PlaySound(this, Map, sound); if (!m_Player) { Delete(); } else { Send(DeathStatus.Instantiate(true)); Warmode = false; BodyMod = 0; // Body = this.Female ? 0x193 : 0x192; Body = Race.GhostBody(this); var deathShroud = new Item(0x204E) { Movable = false, Layer = Layer.OuterTorso }; AddItem(deathShroud); Items.Remove(deathShroud); Items.Insert(0, deathShroud); Poison = null; Combatant = null; Hits = 0; Stam = 0; Mana = 0; EventSink.InvokePlayerDeath(this); ProcessDeltaQueue(); Send(DeathStatus.Instantiate(false)); CheckStatTimers(); } } public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => true; public virtual void Use(Item item) { if (item?.Deleted != false || item.QuestItem || Deleted) return; DisruptiveAction(); if (m_Spell?.OnCasterUsingObject(item) == false) return; var root = item.RootParent; var okay = false; if (!Utility.InUpdateRange(this, item.GetWorldLocation())) { item.OnDoubleClickOutOfRange(this); } else if (!CanSee(item)) { item.OnDoubleClickCantSee(this); } else if (!item.IsAccessibleTo(this)) { var reg = Region.Find(item.GetWorldLocation(), item.Map); if (reg?.SendInaccessibleMessage(item, this) != true) item.OnDoubleClickNotAccessible(this); } else if (!CheckAlive(false)) { item.OnDoubleClickDead(this); } else if (item.InSecureTrade) { item.OnDoubleClickSecureTrade(this); } else if (!AllowItemUse(item)) { } else if (!item.CheckItemUse(this, item)) { } else if (root is Mobile mobile && mobile.IsSnoop(this)) { item.OnSnoop(this); } else if (Region.OnDoubleClick(this, item)) { okay = true; } if (okay) { // TODO: Is this correct? if (!item.Deleted) item.OnItemUsed(this, item); // TODO: Is this correct? if (!item.Deleted) item.OnDoubleClick(this); } } public virtual void Use(Mobile m) { if (m?.Deleted != false || Deleted) return; DisruptiveAction(); if (m_Spell?.OnCasterUsingObject(m) == false) return; if (!Utility.InUpdateRange(this, m)) m.OnDoubleClickOutOfRange(this); else if (!CanSee(m)) m.OnDoubleClickCantSee(this); else if (!CheckAlive(false)) m.OnDoubleClickDead(this); else if (Region.OnDoubleClick(this, m) && !m.Deleted) m.OnDoubleClick(this); } public virtual void Lift(Item item, int amount, out bool rejected, out LRReason reject) { rejected = true; reject = LRReason.Inspecific; if (item == null) return; var from = this; var state = m_NetState; if (from.AccessLevel >= AccessLevel.GameMaster || Core.TickCount - from.NextActionTime >= 0) { if (from.CheckAlive()) { from.DisruptiveAction(); if (from.Holding != null) { reject = LRReason.AreHolding; } else if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(item.GetWorldLocation(), 2)) { reject = LRReason.OutOfRange; } else if (!from.CanSee(item) || !from.InLOS(item)) { reject = LRReason.OutOfSight; } else if (!item.VerifyMove(from)) { reject = LRReason.CannotLift; } else if (!item.IsAccessibleTo(from)) { reject = LRReason.CannotLift; } else if (item.Nontransferable && amount != item.Amount) { if (item.QuestItem) from.SendLocalizedMessage(1074868); // Stacks of quest items cannot be unstacked. reject = LRReason.CannotLift; } else if (!item.CheckLift(from, item, ref reject)) { } else { var root = item.RootParent; if (root is Mobile mobile && !mobile.CheckNonlocalLift(from, item)) { reject = LRReason.TryToSteal; } else if (!from.OnDragLift(item) || !item.OnDragLift(from)) { reject = LRReason.Inspecific; } else if (!from.CheckAlive()) { reject = LRReason.Inspecific; } else { item.SetLastMoved(); if (item.Spawner != null) { item.Spawner.Remove(item); item.Spawner = null; } if (amount == 0) amount = 1; if (amount > item.Amount) amount = item.Amount; var oldAmount = item.Amount; // item.Amount = amount; //Set in LiftItemDupe if (amount < oldAmount) LiftItemDupe(item, amount); // item.Dupe( oldAmount - amount ); var map = from.Map; if (DragEffects && map != null && (root == null || root is Item)) { var eable = map.GetClientsInRange(from.Location); Packet p = null; var rootItem = root as Item; foreach (var ns in eable) if (ns.Mobile != from && ns.Mobile.CanSee(from) && ns.Mobile.InLOS(from) && ns.Mobile.CanSee(root)) { if (p == null) { IEntity src = new Entity(rootItem?.Serial ?? Serial.Zero, rootItem?.Location ?? item.Location, map); p = Packet.Acquire(new DragEffect(src, from, item.ItemID, item.Hue, amount)); } ns.Send(p); } Packet.Release(p); eable.Free(); } var fixLoc = item.Location; var fixMap = item.Map; var shouldFix = item.Parent == null; item.RecordBounce(); item.OnItemLifted(from, item); item.Internalize(); from.Holding = item; var liftSound = item.GetLiftSound(from); if (liftSound != -1) from.Send(new PlaySound(liftSound, from)); from.NextActionTime = Core.TickCount + ActionDelay; if (fixMap != null && shouldFix) fixMap.FixColumn(fixLoc.m_X, fixLoc.m_Y); reject = LRReason.Inspecific; rejected = false; } } } else { reject = LRReason.Inspecific; } } else { SendActionMessage(); reject = LRReason.Inspecific; } if (rejected && state != null) { state.Send(new LiftRej(reject)); if (item.Deleted) return; if (item.Parent is Item) { if (state.ContainerGridLines) state.Send(new ContainerContentUpdate6017(item)); else state.Send(new ContainerContentUpdate(item)); } else if (item.Parent is Mobile) { state.Send(new EquipUpdate(item)); } else { item.SendInfoTo(state); } if (ObjectPropertyList.Enabled && item.Parent != null) state.Send(item.OPLPacket); } } public static Item LiftItemDupe(Item oldItem, int amount) { Item item; try { item = (Item)ActivatorUtil.CreateInstance(oldItem.GetType()); } catch { Console.WriteLine( "Warning: 0x{0:X}: Item must have a zero parameter constructor to be separated from a stack. '{1}'.", oldItem.Serial.Value, oldItem.GetType().Name); return null; } item.Visible = oldItem.Visible; item.Movable = oldItem.Movable; item.LootType = oldItem.LootType; item.Direction = oldItem.Direction; item.Hue = oldItem.Hue; item.ItemID = oldItem.ItemID; item.Location = oldItem.Location; item.Layer = oldItem.Layer; item.Name = oldItem.Name; item.Weight = oldItem.Weight; item.Amount = oldItem.Amount - amount; item.Map = oldItem.Map; oldItem.Amount = amount; oldItem.OnAfterDuped(item); if (oldItem.Parent is Mobile parentMobile) parentMobile.AddItem(item); else if (oldItem.Parent is Item parentItem) parentItem.AddItem(item); item.Delta(ItemDelta.Update); return item; } public virtual void SendDropEffect(Item item) { if (DragEffects && !item.Deleted) { var map = m_Map; var root = item.RootParent; if (map != null && (root == null || root is Item)) { var eable = map.GetClientsInRange(m_Location); Packet p = null; var rootItem = root as Item; foreach (var ns in eable) { if (ns.StygianAbyss) continue; if (ns.Mobile != this && ns.Mobile.CanSee(this) && ns.Mobile.InLOS(this) && ns.Mobile.CanSee(root)) { if (p == null) { IEntity trg = new Entity(rootItem?.Serial ?? Serial.Zero, rootItem?.Location ?? item.Location, map); p = Packet.Acquire(new DragEffect(this, trg, item.ItemID, item.Hue, item.Amount)); } ns.Send(p); } } Packet.Release(p); eable.Free(); } } } public virtual bool Drop(Item to, Point3D loc) { var from = this; var item = from.Holding; var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; from.Holding = null; if (!valid) return false; var bounced = true; item.SetLastMoved(); if (to == null || !item.DropToItem(from, to, loc)) item.Bounce(from); else bounced = false; item.ClearBounce(); if (!bounced) SendDropEffect(item); return !bounced; } public virtual bool Drop(Point3D loc) { var from = this; var item = from.Holding; var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; from.Holding = null; if (!valid) return false; var bounced = true; item.SetLastMoved(); if (!item.DropToWorld(from, loc)) item.Bounce(from); else bounced = false; item.ClearBounce(); if (!bounced) SendDropEffect(item); return !bounced; } public virtual bool Drop(Mobile to, Point3D loc) { var from = this; var item = from.Holding; var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; from.Holding = null; if (!valid) return false; var bounced = true; item.SetLastMoved(); if (to == null || !item.DropToMobile(from, to, loc)) item.Bounce(from); else bounced = false; item.ClearBounce(); if (!bounced) SendDropEffect(item); return !bounced; } public virtual bool MutateSpeech(List hears, ref string text, ref object context) { if (Alive) return false; var sb = new StringBuilder(text.Length, text.Length); for (var i = 0; i < text.Length; ++i) sb.Append(text[i] != ' ' ? GhostChars[Utility.Random(GhostChars.Length)] : ' '); text = sb.ToString(); context = m_GhostMutateContext; return true; } public virtual void Manifest(TimeSpan delay) { Warmode = true; if (m_AutoManifestTimer == null) m_AutoManifestTimer = new AutoManifestTimer(this, delay); else m_AutoManifestTimer.Stop(); m_AutoManifestTimer.Start(); } public virtual bool CheckSpeechManifest() { if (Alive) return false; var delay = AutoManifestTimeout; if (delay > TimeSpan.Zero && (!Warmode || m_AutoManifestTimer != null)) { Manifest(delay); return true; } return false; } public virtual bool CheckHearsMutatedSpeech(Mobile m, object context) => context != m_GhostMutateContext || (m.Alive && !m.CanHearGhosts); private void AddSpeechItemsFrom(List list, Container cont) { for (var i = 0; i < cont.Items.Count; ++i) { var item = cont.Items[i]; if (item.HandlesOnSpeech) list.Add(item); if (item is Container container) AddSpeechItemsFrom(list, container); } } public virtual void DoSpeech(string text, int[] keywords, MessageType type, int hue) { if (Deleted || CommandSystem.Handle(this, text, type)) return; var range = 15; switch (type) { case MessageType.Regular: SpeechHue = hue; break; case MessageType.Emote: EmoteHue = hue; break; case MessageType.Whisper: WhisperHue = hue; range = 1; break; case MessageType.Yell: YellHue = hue; range = 18; break; case MessageType.System: break; case MessageType.Label: break; case MessageType.Focus: break; case MessageType.Spell: break; case MessageType.Guild: break; case MessageType.Alliance: break; case MessageType.Command: break; case MessageType.Encoded: break; default: type = MessageType.Regular; break; } var regArgs = new SpeechEventArgs(this, text, type, hue, keywords); EventSink.InvokeSpeech(regArgs); Region.OnSpeech(regArgs); OnSaid(regArgs); if (regArgs.Blocked) return; text = regArgs.Speech; if (string.IsNullOrEmpty(text)) return; var hears = m_Hears; var onSpeech = m_OnSpeech; if (m_Map != null) { var eable = m_Map.GetObjectsInRange(m_Location, range); foreach (var o in eable) if (o is Mobile heard) { if (!heard.CanSee(this) || (!NoSpeechLOS && heard.Player && !heard.InLOS(this))) continue; if (heard.m_NetState != null) hears.Add(heard); if (heard.HandlesOnSpeech(this)) onSpeech.Add(heard); for (var i = 0; i < heard.Items.Count; ++i) { var item = heard.Items[i]; if (item.HandlesOnSpeech) onSpeech.Add(item); if (item is Container container) AddSpeechItemsFrom(onSpeech, container); } } else if (o is Item item) { if (item.HandlesOnSpeech) onSpeech.Add(item); if (item is Container container) AddSpeechItemsFrom(onSpeech, container); } eable.Free(); object mutateContext = null; var mutatedText = text; SpeechEventArgs mutatedArgs = null; if (MutateSpeech(hears, ref mutatedText, ref mutateContext)) mutatedArgs = new SpeechEventArgs(this, mutatedText, type, hue, Array.Empty()); CheckSpeechManifest(); ProcessDelta(); Packet regp = null; Packet mutp = null; // TODO: Should this be sorted like onSpeech is below? for (var i = 0; i < hears.Count; ++i) { var heard = hears[i]; if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) { heard.OnSpeech(regArgs); var ns = heard.NetState; if (ns != null) { regp ??= Packet.Acquire(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); ns.Send(regp); } } else { heard.OnSpeech(mutatedArgs); var ns = heard.NetState; if (ns != null) { mutp ??= Packet.Acquire(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, mutatedText)); ns.Send(mutp); } } } Packet.Release(regp); Packet.Release(mutp); if (onSpeech.Count > 1) onSpeech.Sort(LocationComparer.GetInstance(this)); for (var i = 0; i < onSpeech.Count; ++i) { var obj = onSpeech[i]; if (obj is Mobile heard) { if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) heard.OnSpeech(regArgs); else heard.OnSpeech(mutatedArgs); } else { ((Item)obj).OnSpeech(regArgs); } } if (m_Hears.Count > 0) m_Hears.Clear(); if (m_OnSpeech.Count > 0) m_OnSpeech.Clear(); } } public static Mobile GetDamagerFrom(DamageEntry de) => de?.Damager; public Mobile FindMostRecentDamager(bool allowSelf) => GetDamagerFrom(FindMostRecentDamageEntry(allowSelf)); public DamageEntry FindMostRecentDamageEntry(bool allowSelf) { for (var i = DamageEntries.Count - 1; i >= 0; --i) { if (i >= DamageEntries.Count) continue; var de = DamageEntries[i]; if (de.HasExpired) DamageEntries.RemoveAt(i); else if (allowSelf || de.Damager != this) return de; } return null; } public Mobile FindLeastRecentDamager(bool allowSelf) => GetDamagerFrom(FindLeastRecentDamageEntry(allowSelf)); public DamageEntry FindLeastRecentDamageEntry(bool allowSelf) { for (var i = 0; i < DamageEntries.Count; ++i) { if (i < 0) continue; var de = DamageEntries[i]; if (de.HasExpired) { DamageEntries.RemoveAt(i); --i; } else if (allowSelf || de.Damager != this) { return de; } } return null; } public Mobile FindMostTotalDamager(bool allowSelf) => GetDamagerFrom(FindMostTotalDamageEntry(allowSelf)); public DamageEntry FindMostTotalDamageEntry(bool allowSelf) { DamageEntry mostTotal = null; for (var i = DamageEntries.Count - 1; i >= 0; --i) { if (i >= DamageEntries.Count) continue; var de = DamageEntries[i]; if (de.HasExpired) DamageEntries.RemoveAt(i); else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven > mostTotal.DamageGiven)) mostTotal = de; } return mostTotal; } public Mobile FindLeastTotalDamager(bool allowSelf) => GetDamagerFrom(FindLeastTotalDamageEntry(allowSelf)); public DamageEntry FindLeastTotalDamageEntry(bool allowSelf) { DamageEntry mostTotal = null; for (var i = DamageEntries.Count - 1; i >= 0; --i) { if (i >= DamageEntries.Count) continue; var de = DamageEntries[i]; if (de.HasExpired) DamageEntries.RemoveAt(i); else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven < mostTotal.DamageGiven)) mostTotal = de; } return mostTotal; } public DamageEntry FindDamageEntryFor(Mobile m) { for (var i = DamageEntries.Count - 1; i >= 0; --i) { if (i >= DamageEntries.Count) continue; var de = DamageEntries[i]; if (de.HasExpired) DamageEntries.RemoveAt(i); else if (de.Damager == m) return de; } return null; } public virtual Mobile GetDamageMaster(Mobile damagee) => null; public virtual DamageEntry RegisterDamage(int amount, Mobile from) { var de = FindDamageEntryFor(from) ?? new DamageEntry(from); de.DamageGiven += amount; de.LastDamage = DateTime.UtcNow; DamageEntries.Remove(de); DamageEntries.Add(de); var master = from.GetDamageMaster(this); if (master != null) { var list = de.Responsible; if (list == null) de.Responsible = list = new List(); var resp = list.FirstOrDefault(check => check.Damager == master); if (resp == null) list.Add(resp = new DamageEntry(master)); resp.DamageGiven += amount; resp.LastDamage = DateTime.UtcNow; } return de; } /// /// Overridable. Virtual event invoked when the Mobile is damaged. It is called before /// hit points are lowered or the Mobile is killed. /// /// /// /// public virtual void OnDamage(int amount, Mobile from, bool willKill) { } public virtual void Damage(int amount) { Damage(amount, null); } public virtual bool CanBeDamaged() => !m_Blessed; public virtual void Damage(int amount, Mobile from) { Damage(amount, from, true); } public virtual void Damage(int amount, Mobile from, bool informMount) { if (!CanBeDamaged() || Deleted) return; if (!Region.OnDamage(this, ref amount)) return; if (amount > 0) { var oldHits = Hits; var newHits = oldHits - amount; m_Spell?.OnCasterHurt(); // if (m_Spell != null && m_Spell.State == SpellState.Casting) // m_Spell.Disturb( DisturbType.Hurt, false, true ); if (from != null) RegisterDamage(amount, from); DisruptiveAction(); Paralyzed = false; switch (VisibleDamageType) { case VisibleDamageType.Related: { SendVisibleDamageRelated(from, amount); break; } case VisibleDamageType.Everyone: { SendVisibleDamageEveryone(amount); break; } case VisibleDamageType.Selective: { SendVisibleDamageSelective(from, amount); break; } } OnDamage(amount, from, newHits < 0); var m = Mount; if (m != null && informMount) m.OnRiderDamaged(amount, from, newHits < 0); if (newHits < 0) { LastKiller = from; Hits = 0; if (oldHits >= 0) Kill(); } else { Hits = newHits; } } } public void SendVisibleDamageRelated(Mobile from, int amount) { NetState ourState = m_NetState, theirState = from?.m_NetState; if (ourState == null) { var master = GetDamageMaster(from); if (master != null) ourState = master.m_NetState; } if (theirState == null && from != null) { var master = from.GetDamageMaster(this); if (master != null) theirState = master.m_NetState; } if (amount > 0 && (ourState != null || theirState != null)) { Packet p = null; // = new DamagePacket( this, amount ); if (ourState != null) { p = ourState.DamagePacket ? Packet.Acquire(new DamagePacket(this, amount)) : Packet.Acquire(new DamagePacketOld(this, amount)); ourState.Send(p); } if (theirState != null && theirState != ourState) { var newPacket = theirState.DamagePacket; if (newPacket && !(p is DamagePacket)) { Packet.Release(p); p = Packet.Acquire(new DamagePacket(this, amount)); } else if (!newPacket && !(p is DamagePacketOld)) { Packet.Release(p); p = Packet.Acquire(new DamagePacketOld(this, amount)); } theirState.Send(p); } Packet.Release(p); } } public void SendVisibleDamageEveryone(int amount) { if (amount < 0) return; var map = m_Map; if (map == null) return; var eable = map.GetClientsInRange(m_Location); Packet pNew = null; Packet pOld = null; foreach (var ns in eable) if (ns.Mobile.CanSee(this)) { if (ns.DamagePacket) { pNew ??= Packet.Acquire(new DamagePacket(this, amount)); ns.Send(pNew); } else { pOld ??= Packet.Acquire(new DamagePacketOld(this, amount)); ns.Send(pOld); } } Packet.Release(pNew); Packet.Release(pOld); eable.Free(); } public void SendVisibleDamageSelective(Mobile from, int amount) { NetState ourState = m_NetState, theirState = from?.m_NetState; var damager = from; var damaged = this; if (ourState == null) { var master = GetDamageMaster(from); if (master != null) { damaged = master; ourState = master.m_NetState; } } if (!damaged.ShowVisibleDamage) return; if (theirState == null && from != null) { var master = from.GetDamageMaster(this); if (master != null) { damager = master; theirState = master.m_NetState; } } if (amount > 0 && (ourState != null || theirState != null)) { if (damaged.CanSeeVisibleDamage && ourState != null) { if (ourState.DamagePacket) ourState.Send(new DamagePacket(this, amount)); else ourState.Send(new DamagePacketOld(this, amount)); } if (theirState != null && theirState != ourState && damager.CanSeeVisibleDamage) { if (theirState.DamagePacket) theirState.Send(new DamagePacket(this, amount)); else theirState.Send(new DamagePacketOld(this, amount)); } } } public void Heal(int amount) { Heal(amount, this, true); } public void Heal(int amount, Mobile from) { Heal(amount, from, true); } public void Heal(int amount, Mobile from, bool message) { if (!Alive || IsDeadBondedPet) return; if (!Region.OnHeal(this, ref amount)) return; OnHeal(ref amount, from); if (Hits + amount > HitsMax) amount = HitsMax - Hits; Hits += amount; if (message && amount > 0) m_NetState?.Send(new MessageLocalizedAffix(Serial.MinusOne, -1, MessageType.Label, 0x3B2, 3, 1008158, "", AffixType.Append | AffixType.System, amount.ToString(), "")); } public virtual void OnHeal(ref int amount, Mobile from) { } public virtual void Deserialize(IGenericReader reader) { var version = reader.ReadInt(); switch (version) { case 32: { // Removed StuckMenu goto case 31; } case 31: { LastStrGain = reader.ReadDeltaTime(); LastIntGain = reader.ReadDeltaTime(); LastDexGain = reader.ReadDeltaTime(); goto case 30; } case 30: { var hairflag = reader.ReadByte(); if ((hairflag & 0x01) != 0) m_Hair = new HairInfo(reader); if ((hairflag & 0x02) != 0) m_FacialHair = new FacialHairInfo(reader); goto case 29; } case 29: { m_Race = reader.ReadRace(); goto case 28; } case 28: { if (version <= 30) LastStatGain = reader.ReadDeltaTime(); goto case 27; } case 27: { m_TithingPoints = reader.ReadInt(); goto case 26; } case 26: case 25: case 24: { Corpse = reader.ReadItem() as Container; goto case 23; } case 23: { CreationTime = reader.ReadDateTime(); goto case 22; } case 22: // Just removed followers case 21: { Stabled = reader.ReadStrongMobileList(); goto case 20; } case 20: { CantWalk = reader.ReadBool(); goto case 19; } case 19: // Just removed variables case 18: { Virtues = new VirtueInfo(reader); goto case 17; } case 17: { Thirst = reader.ReadInt(); BAC = reader.ReadInt(); goto case 16; } case 16: { m_ShortTermMurders = reader.ReadInt(); if (version <= 24) { reader.ReadDateTime(); reader.ReadDateTime(); } goto case 15; } case 15: { if (version < 22) reader.ReadInt(); // followers m_FollowersMax = reader.ReadInt(); goto case 14; } case 14: { MagicDamageAbsorb = reader.ReadInt(); goto case 13; } case 13: { GuildFealty = reader.ReadMobile(); goto case 12; } case 12: { m_Guild = reader.ReadGuild(); goto case 11; } case 11: { m_DisplayGuildTitle = reader.ReadBool(); goto case 10; } case 10: { CanSwim = reader.ReadBool(); goto case 9; } case 9: { Squelched = reader.ReadBool(); goto case 8; } case 8: { m_Holding = reader.ReadItem(); goto case 7; } case 7: { m_VirtualArmor = reader.ReadInt(); goto case 6; } case 6: { BaseSoundID = reader.ReadInt(); goto case 5; } case 5: { DisarmReady = reader.ReadBool(); StunReady = reader.ReadBool(); goto case 4; } case 4: { if (version <= 25) Poison.Deserialize(reader); goto case 3; } case 3: { m_StatCap = reader.ReadInt(); goto case 2; } case 2: { NameHue = reader.ReadInt(); goto case 1; } case 1: { m_Hunger = reader.ReadInt(); goto case 0; } case 0: { if (version < 21) Stabled = new List(); if (version < 18) Virtues = new VirtueInfo(); if (version < 11) m_DisplayGuildTitle = true; if (version < 3) m_StatCap = 225; if (version < 15) { m_Followers = 0; m_FollowersMax = 5; } m_Location = reader.ReadPoint3D(); m_Body = new Body(reader.ReadInt()); m_Name = reader.ReadString(); m_GuildTitle = reader.ReadString(); m_Criminal = reader.ReadBool(); m_Kills = reader.ReadInt(); SpeechHue = reader.ReadInt(); EmoteHue = reader.ReadInt(); WhisperHue = reader.ReadInt(); YellHue = reader.ReadInt(); m_Language = reader.ReadString(); m_Female = reader.ReadBool(); m_Warmode = reader.ReadBool(); m_Hidden = reader.ReadBool(); m_Direction = (Direction)reader.ReadByte(); m_Hue = reader.ReadInt(); m_Str = reader.ReadInt(); m_Dex = reader.ReadInt(); m_Int = reader.ReadInt(); m_Hits = reader.ReadInt(); m_Stam = reader.ReadInt(); m_Mana = reader.ReadInt(); m_Map = reader.ReadMap(); m_Blessed = reader.ReadBool(); m_Fame = reader.ReadInt(); m_Karma = reader.ReadInt(); m_AccessLevel = (AccessLevel)reader.ReadByte(); Skills = new Skills(this, reader); Items = reader.ReadStrongItemList(); m_Player = reader.ReadBool(); m_Title = reader.ReadString(); Profile = reader.ReadString(); ProfileLocked = reader.ReadBool(); if (version <= 18) { reader.ReadInt(); reader.ReadInt(); reader.ReadInt(); } AutoPageNotify = reader.ReadBool(); LogoutLocation = reader.ReadPoint3D(); LogoutMap = reader.ReadMap(); m_StrLock = (StatLockType)reader.ReadByte(); m_DexLock = (StatLockType)reader.ReadByte(); m_IntLock = (StatLockType)reader.ReadByte(); StatMods = new List(); SkillMods = new List(); if (version < 32) if (reader.ReadBool()) { var count = reader.ReadInt(); for (var i = 0; i < count; ++i) reader.ReadDateTime(); } if (m_Player && m_Map != Map.Internal) { LogoutLocation = m_Location; LogoutMap = m_Map; m_Map = Map.Internal; } m_Map?.OnEnter(this); if (m_Criminal) { m_ExpireCriminal ??= new ExpireCriminalTimer(this); m_ExpireCriminal.Start(); } if (ShouldCheckStatTimers) CheckStatTimers(); if (!m_Player && m_Dex <= 100 && m_CombatTimer != null) m_CombatTimer.Priority = TimerPriority.FiftyMS; else if (m_CombatTimer != null) m_CombatTimer.Priority = TimerPriority.EveryTick; UpdateRegion(); UpdateResistances(); break; } } if (!m_Player) Utility.Intern(ref m_Name); Utility.Intern(ref m_Title); Utility.Intern(ref m_Language); } public void ConvertHair() { Item hair; if ((hair = FindItemOnLayer(Layer.Hair)) != null) { HairItemID = hair.ItemID; HairHue = hair.Hue; hair.Delete(); } if ((hair = FindItemOnLayer(Layer.FacialHair)) != null) { FacialHairItemID = hair.ItemID; FacialHairHue = hair.Hue; hair.Delete(); } } public virtual void CheckStatTimers() { if (Deleted) return; if (Hits < HitsMax) { if (CanRegenHits) { m_HitsTimer ??= new HitsTimer(this); m_HitsTimer.Start(); } else { m_HitsTimer?.Stop(); } } else { Hits = HitsMax; } if (Stam < StamMax) { if (CanRegenStam) { m_StamTimer ??= new StamTimer(this); m_StamTimer.Start(); } else { m_StamTimer?.Stop(); } } else { Stam = StamMax; } if (Mana < ManaMax) { if (CanRegenMana) { m_ManaTimer ??= new ManaTimer(this); m_ManaTimer.Start(); } else { m_ManaTimer?.Stop(); } } else { Mana = ManaMax; } } public static string GetAccessLevelName(AccessLevel level) => m_AccessLevelNames[(int)level]; public virtual bool CanPaperdollBeOpenedBy(Mobile from) => Body.IsHuman || Body.IsGhost || IsBodyMod; public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) { } public virtual void GetContextMenuEntries(Mobile from, List list) { if (Deleted) return; if (CanPaperdollBeOpenedBy(from)) list.Add(new PaperdollEntry(this)); if (from == this && Backpack != null && CanSee(Backpack) && CheckAlive(false)) list.Add(new OpenBackpackEntry(this)); } public void Internalize() { Map = Map.Internal; } /// /// Overridable. Virtual event invoked when is added from the Mobile, such /// as when it is equipped. /// /// /// public virtual void OnItemAdded(Item item) { } /// /// Overridable. Virtual event invoked when is removed from the Mobile. /// /// /// public virtual void OnItemRemoved(Item item) { } /// /// Overridable. Virtual event invoked when is becomes a child of the Mobile; it's worn or contained /// at some level of the Mobile's backpack or bank box /// /// /// public virtual void OnSubItemAdded(Item item) { } /// /// Overridable. Virtual event invoked when is removed from the Mobile, its /// backpack, or its bank box. /// /// /// public virtual void OnSubItemRemoved(Item item) { } public virtual void OnItemBounceCleared(Item item) { } public virtual void OnSubItemBounceCleared(Item item) { } public void AddItem(Item item) { if (item?.Deleted != false) return; if (item.Parent == this) return; if (item.Parent is Mobile parentMobile) parentMobile.RemoveItem(item); else if (item.Parent is Item parentItem) parentItem.RemoveItem(item); else item.SendRemovePacket(); item.Parent = this; item.Map = m_Map; Items.Add(item); if (!item.IsVirtualItem) { UpdateTotal(item, TotalType.Gold, item.TotalGold); UpdateTotal(item, TotalType.Items, item.TotalItems + 1); UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); } item.Delta(ItemDelta.Update); item.OnAdded(this); OnItemAdded(item); if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || item.PoisonResistance != 0 || item.EnergyResistance != 0) UpdateResistances(); } public void RemoveItem(Item item) { if (item == null || Items == null) return; if (Items.Contains(item)) { item.SendRemovePacket(); // int oldCount = m_Items.Count; Items.Remove(item); if (!item.IsVirtualItem) { UpdateTotal(item, TotalType.Gold, -item.TotalGold); UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); } item.Parent = null; item.OnRemoved(this); OnItemRemoved(item); if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || item.PoisonResistance != 0 || item.EnergyResistance != 0) UpdateResistances(); } } public virtual void Animate(int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay) { var map = m_Map; if (map == null) return; ProcessDelta(); Packet p = null; // Packet pNew = null; var eable = map.GetClientsInRange(m_Location); foreach (var state in eable) if (state.Mobile.CanSee(this)) { state.Mobile.ProcessDelta(); // if (state.StygianAbyss ) { // if (pNew == null) // pNew = Packet.Acquire( new NewMobileAnimation( this, action, frameCount, delay ) ); // state.Send( pNew ); // } else { if (p == null) { if (Body.IsGargoyle) { frameCount = 10; if (Flying) { if (action >= 9 && action <= 11) action = 71; else if (action >= 12 && action <= 14) action = 72; else if (action == 20) action = 77; else if (action == 31) action = 71; else if (action == 34) action = 78; else if (action >= 200 && action <= 259) action = 75; else if (action >= 260 && action <= 270) action = 75; } else { if (action >= 200 && action <= 259) action = 17; else if (action >= 260 && action <= 270) action = 16; } } p = Packet.Acquire(new MobileAnimation(this, action, frameCount, repeatCount, forward, repeat, delay)); } state.Send(p); // } } Packet.Release(p); // Packet.Release( pNew ); eable.Free(); } public void SendSound(int soundID) { if (soundID != -1 && m_NetState != null) Send(new PlaySound(soundID, this)); } public void SendSound(int soundID, IPoint3D p) { if (soundID != -1 && m_NetState != null) Send(new PlaySound(soundID, p)); } public void PlaySound(int soundID) { if (soundID == -1 || m_Map == null) return; var p = Packet.Acquire(new PlaySound(soundID, this)); var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state.Mobile.CanSee(this)) state.Send(p); Packet.Release(p); eable.Free(); } public virtual void OnAccessLevelChanged(AccessLevel oldLevel) { } public virtual void OnFameChange(int oldValue) { } public virtual void OnKarmaChange(int oldValue) { } // Mobile did something which should unhide him public virtual void RevealingAction() { if (m_Hidden && m_AccessLevel == AccessLevel.Player) Hidden = false; DisruptiveAction(); // Anything that unhides you will also distrupt meditation } public void SendRemovePacket() { SendRemovePacket(true); } public void SendRemovePacket(bool everyone) { if (m_Map == null) return; var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state != m_NetState && (everyone || !state.Mobile.CanSee(this))) state.Send(RemovePacket); eable.Free(); } public void ClearScreen() { if (m_Map == null || m_NetState == null) return; var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); foreach (var o in eable) if (o is Mobile m) { if (m != this && Utility.InUpdateRange(m_Location, m.m_Location)) m_NetState.Send(m.RemovePacket); } else if (o is Item item) { if (InRange(item.Location, item.GetUpdateRange(this))) m_NetState.Send(item.RemovePacket); } eable.Free(); } public bool Send(Packet p) => Send(p, false); public bool Send(Packet p, bool throwOnOffline) { if (m_NetState != null) { m_NetState.Send(p); return true; } if (throwOnOffline) throw new MobileNotConnectedException(this, "Packet could not be sent."); return false; } /// /// Overridable. Event invoked before the Mobile says something. /// /// public virtual void OnSaid(SpeechEventArgs e) { if (Squelched) { if (Core.ML) SendLocalizedMessage(500168); // You can not say anything, you have been muted. else SendMessage("You can not say anything, you have been squelched."); // Cliloc ITSELF changed during ML. e.Blocked = true; } if (!e.Blocked) RevealingAction(); } public virtual bool HandlesOnSpeech(Mobile from) => false; /// /// Overridable. Virtual event invoked when the Mobile hears speech. This event will only be invoked if /// returns true. /// /// public virtual void OnSpeech(SpeechEventArgs e) { } public void SendEverything() { var ns = m_NetState; if (m_Map != null && ns != null) { var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); foreach (var o in eable) if (o is Item item) { if (CanSee(item) && InRange(item.Location, item.GetUpdateRange(this))) item.SendInfoTo(ns); } else if (o is Mobile m) { if (CanSee(m) && Utility.InUpdateRange(m_Location, m.m_Location)) { ns.Send(MobileIncoming.Create(ns, this, m)); if (ns.StygianAbyss) { if (m.Poisoned) ns.Send(new HealthbarPoison(m)); if (m.Blessed || m.YellowHealthbar) ns.Send(new HealthbarYellow(m)); } if (m.IsDeadBondedPet) ns.Send(new BondedStatus(m.Serial, true)); if (ObjectPropertyList.Enabled) ns.Send(m.OPLPacket); } } eable.Free(); } } public void UpdateRegion() { if (Deleted) return; var newRegion = Region.Find(m_Location, m_Map); if (newRegion != m_Region) { Region.OnRegionChange(this, m_Region, newRegion); m_Region = newRegion; OnRegionChange(m_Region, newRegion); } } /// /// Overridable. Virtual event invoked when changes. /// protected virtual void OnMapChange(Map oldMap) { } public void SetDirection(Direction dir) { m_Direction = dir; } public virtual int GetSeason() => m_Map?.Season ?? 1; public virtual int GetPacketFlags() { var flags = 0x0; if (m_Paralyzed || m_Frozen) flags |= 0x01; if (m_Female) flags |= 0x02; if (m_Flying) flags |= 0x04; if (m_Blessed || m_YellowHealthbar) flags |= 0x08; if (m_Warmode) flags |= 0x40; if (m_Hidden) flags |= 0x80; return flags; } // Pre-7.0.0.0 Packet Flags public virtual int GetOldPacketFlags() { var flags = 0x0; if (m_Paralyzed || m_Frozen) flags |= 0x01; if (m_Female) flags |= 0x02; if (m_Poison != null) flags |= 0x04; if (m_Blessed || m_YellowHealthbar) flags |= 0x08; if (m_Warmode) flags |= 0x40; if (m_Hidden) flags |= 0x80; return flags; } public virtual void OnGenderChanged(bool oldFemale) { } public virtual void ToggleFlying() { } /// /// Overridable. Virtual event invoked after the Warmode property has changed. /// public virtual void OnWarmodeChanged() { } public virtual void OnHiddenChanged() { AllowedStealthSteps = 0; if (m_Map == null) return; var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (!state.Mobile.CanSee(this)) { state.Send(RemovePacket); } else { state.Send(MobileIncoming.Create(state, state.Mobile, this)); if (IsDeadBondedPet) state.Send(new BondedStatus(Serial, true)); if (ObjectPropertyList.Enabled) state.Send(OPLPacket); } eable.Free(); } public virtual void OnConnected() { } public virtual void OnDisconnected() { } public virtual void OnNetStateChanged() { } public virtual bool CanSee(object o) { if (o is Item item) return CanSee(item); if (o is Mobile mobile) return CanSee(mobile); return true; } public virtual bool CanSee(Item item) { if (m_Map == Map.Internal) return false; if (item.Map == Map.Internal) return false; if (item.Parent != null) { if (item.Parent is Item parent) { if (!(CanSee(parent) && parent.IsChildVisibleTo(this, item))) return false; } else if (item.Parent is Mobile mobile) { if (!CanSee(mobile)) return false; } } if (item is BankBox box && m_AccessLevel <= AccessLevel.Counselor && (box.Owner != this || !box.Opened)) return false; if (item is SecureTradeContainer container) { var trade = container.Trade; if (trade != null && trade.From.Mobile != this && trade.To.Mobile != this) return false; } return !item.Deleted && item.Map == m_Map && (item.Visible || m_AccessLevel > AccessLevel.Counselor); } public virtual bool CanSee(Mobile m) { if (Deleted || m.Deleted || m_Map == Map.Internal || m.m_Map == Map.Internal) return false; return this == m || (m.m_Map == m_Map && (!m.Hidden || (m_AccessLevel != AccessLevel.Player && (m_AccessLevel >= m.AccessLevel || m_AccessLevel >= AccessLevel.Administrator))) && (m.Alive || (Core.SE && Skills.SpiritSpeak.Value >= 100.0) || !Alive || m_AccessLevel > AccessLevel.Player || m.Warmode)); } public virtual bool CanBeRenamedBy(Mobile from) => from.AccessLevel >= AccessLevel.GameMaster && from.m_AccessLevel > m_AccessLevel; public virtual void OnGuildTitleChange(string oldTitle) { } public virtual void OnAfterNameChange(string oldName, string newName) { } public virtual void OnGuildChange(BaseGuild oldGuild) { } public virtual int SafeBody(int body) { var delta = -1; for (var i = 0; delta < 0 && i < m_InvalidBodies.Length; ++i) delta = m_InvalidBodies[i] - body; return delta != 0 ? body : 0; } public virtual void FreeCache() { StaticPacketHandlers.FreeRemoveItemPacket(this); StaticPacketHandlers.FreeOPLInfoPacket(this); ReleaseOPLPacket(); } public ObjectPropertyList NewObjectPropertyList() { var list = new ObjectPropertyList(this); GetProperties(list); list.Terminate(); list.SetStatic(); return list; } public void ClearProperties() { ReleaseOPLPacket(); StaticPacketHandlers.FreeOPLInfoPacket(this); } public void InvalidateProperties() { if (!ObjectPropertyList.Enabled) return; if (m_Map != null && m_Map != Map.Internal && !World.Loading) { var oldList = m_PropertyList; m_PropertyList = null; if (oldList != null && oldList.Hash != PropertyList.Hash) { StaticPacketHandlers.FreeOPLInfoPacket(this); Delta(MobileDelta.Properties); } } else { ClearProperties(); } } public virtual void SetLocation(Point3D newLocation, bool isTeleport) { if (Deleted) return; var oldLocation = m_Location; if (oldLocation == newLocation) return; m_Location = newLocation; UpdateRegion(); var box = FindBankNoCreate(); if (box?.Opened == true) box.Close(); m_NetState?.ValidateAllTrades(); m_Map?.OnMove(oldLocation, this); if (isTeleport && m_NetState != null && (!m_NetState.HighSeas || !NoMoveHS)) { m_NetState.Sequence = 0; if (m_NetState.StygianAbyss) m_NetState.Send(new MobileUpdate(this)); else m_NetState.Send(new MobileUpdateOld(this)); ClearFastwalkStack(); } var map = m_Map; if (map != null) { // First, send a remove message to everyone who can no longer see us. (inOldRange && !inNewRange) var eable = map.GetClientsInRange(oldLocation); foreach (var ns in eable) if (ns != m_NetState && !Utility.InUpdateRange(newLocation, ns.Mobile.Location)) ns.Send(RemovePacket); eable.Free(); var ourState = m_NetState; // Check to see if we are attached to a client if (ourState != null) { var eeable = map.GetObjectsInRange(newLocation, Core.GlobalMaxUpdateRange); // We are attached to a client, so it's a bit more complex. We need to send new items and people to ourself, and ourself to other clients foreach (var o in eeable) if (o is Item item) { var range = item.GetUpdateRange(this); var loc = item.Location; if (!Utility.InRange(oldLocation, loc, range) && Utility.InRange(newLocation, loc, range) && CanSee(item)) item.SendInfoTo(ourState); } else if (o != this && o is Mobile m) { if (!Utility.InUpdateRange(newLocation, m.m_Location)) continue; var inOldRange = Utility.InUpdateRange(oldLocation, m.m_Location); if (m.m_NetState != null && ((isTeleport && (!m.m_NetState.HighSeas || !NoMoveHS)) || !inOldRange) && m.CanSee(this)) { m.m_NetState.Send(MobileIncoming.Create(m.m_NetState, m, this)); if (m.m_NetState.StygianAbyss) { // if (m_Poison != null) m.m_NetState.Send(new HealthbarPoison(this)); // if (m_Blessed || m_YellowHealthbar) m.m_NetState.Send(new HealthbarYellow(this)); } if (IsDeadBondedPet) m.m_NetState.Send(new BondedStatus(Serial, true)); if (ObjectPropertyList.Enabled) m.m_NetState.Send(OPLPacket); } if (inOldRange || !CanSee(m)) continue; ourState.Send(MobileIncoming.Create(ourState, this, m)); if (ourState.StygianAbyss) { // if (m.Poisoned) ourState.Send(new HealthbarPoison(m)); // if (m.Blessed || m.YellowHealthbar) ourState.Send(new HealthbarYellow(m)); } if (m.IsDeadBondedPet) ourState.Send(new BondedStatus(m.Serial, true)); if (ObjectPropertyList.Enabled) ourState.Send(m.OPLPacket); } eeable.Free(); } else { eable = map.GetClientsInRange(newLocation); // We're not attached to a client, so simply send an Incoming foreach (var ns in eable) if (((isTeleport && (!ns.HighSeas || !NoMoveHS)) || !Utility.InUpdateRange(oldLocation, ns.Mobile.Location)) && ns.Mobile.CanSee(this)) { ns.Send(MobileIncoming.Create(ns, ns.Mobile, this)); if (ns.StygianAbyss) { // if (m_Poison != null) ns.Send(new HealthbarPoison(this)); // if (m_Blessed || m_YellowHealthbar) ns.Send(new HealthbarYellow(this)); } if (IsDeadBondedPet) ns.Send(new BondedStatus(Serial, true)); if (ObjectPropertyList.Enabled) ns.Send(OPLPacket); } eable.Free(); } } OnLocationChange(oldLocation); Region.OnLocationChanged(this, oldLocation); } /// /// Overridable. Virtual event invoked when changes. /// protected virtual void OnLocationChange(Point3D oldLocation) { } public bool HasFreeHand() => FindItemOnLayer(Layer.TwoHanded) == null; public virtual IWeapon GetDefaultWeapon() => DefaultWeapon; public BankBox FindBankNoCreate() { if (m_BankBox?.Deleted != false || m_BankBox.Parent != this) m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; return m_BankBox; } public Item FindItemOnLayer(Layer layer) { var eq = Items; var count = eq.Count; for (var i = 0; i < count; ++i) { var item = eq[i]; if (!item.Deleted && item.Layer == layer) return item; } return null; } public void SendIncomingPacket() { if (m_Map == null) return; var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state.Mobile.CanSee(this)) { state.Send(MobileIncoming.Create(state, state.Mobile, this)); if (state.StygianAbyss) { if (m_Poison != null) state.Send(new HealthbarPoison(this)); if (m_Blessed || m_YellowHealthbar) state.Send(new HealthbarYellow(this)); } if (IsDeadBondedPet) state.Send(new BondedStatus(Serial, true)); if (ObjectPropertyList.Enabled) state.Send(OPLPacket); } eable.Free(); } public bool PlaceInBackpack(Item item) { if (item.Deleted) return false; return Backpack?.TryDropItem(this, item, false) == true; } public bool AddToBackpack(Item item) { if (item.Deleted) return false; if (!PlaceInBackpack(item)) { var loc = m_Location; var map = m_Map; if ((map == null || map == Map.Internal) && LogoutMap != null) { loc = LogoutLocation; map = LogoutMap; } item.MoveToWorld(loc, map); return false; } return true; } public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => true; public virtual bool CheckNonlocalLift(Mobile from, Item item) => from == this || (from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster); public virtual bool CheckTrade(Mobile to, Item item, SecureTradeContainer cont, bool message, bool checkItems, int plusItems, int plusWeight) => true; public virtual bool OpenTrade(Mobile from, Item offer = null) { if (!from.Player || !Player || !from.Alive || !Alive) return false; var ourState = m_NetState; var theirState = from.m_NetState; if (ourState == null || theirState == null) return false; var cont = theirState.FindTradeContainer(this); if (!from.CheckTrade(this, offer, cont, true, true, 0, 0)) return false; cont ??= theirState.AddTrade(ourState); if (offer != null) cont.DropItem(offer); return true; } /// /// Overridable. Event invoked when a Mobile () drops an /// /// /// /// onto the Mobile. /// public virtual bool OnDragDrop(Mobile from, Item dropped) { if (from == this) { var pack = Backpack; return pack != null && dropped.DropToItem(from, pack, new Point3D(-1, -1, 0)); } return from.InRange(Location, 2) && OpenTrade(from, dropped); } public virtual bool CheckEquip(Item item) { for (var i = 0; i < Items.Count; ++i) if (Items[i].CheckConflictingLayer(this, item, item.Layer) || item.CheckConflictingLayer(this, Items[i], Items[i].Layer)) return false; return true; } /// /// Overridable. Virtual event invoked when the Mobile attempts to wear . /// /// True if the request is accepted, false if otherwise. public virtual bool OnEquip(Item item) { // For some reason OSI allows equipping quest items, but they are unmarked in the process if (item.QuestItem) { item.QuestItem = false; SendLocalizedMessage( 1074769); // An item must be in your backpack (and not in a container within) to be toggled as a quest item. } return true; } /// /// Overridable. Virtual event invoked when the Mobile attempts to lift . /// /// True if the lift is allowed, false if otherwise. /// /// The following example demonstrates usage. It will disallow any attempts to pick up a pick axe if the Mobile does not have /// enough strength. /// /// public override bool OnDragLift( Item item ) /// { /// if (item is Pickaxe && this.Str < 60) /// { /// SendMessage( "That is too heavy for you to lift." ); /// return false; /// } /// /// return base.OnDragLift( item ); /// } /// public virtual bool OnDragLift(Item item) => true; /// /// Overridable. Virtual event invoked when the Mobile attempts to drop into a /// /// /// /// . /// /// True if the drop is allowed, false if otherwise. public virtual bool OnDroppedItemInto(Item item, Container container, Point3D loc) => true; /// /// Overridable. Virtual event invoked when the Mobile attempts to drop directly onto another /// , . This is the case of stacking items. /// /// True if the drop is allowed, false if otherwise. public virtual bool OnDroppedItemOnto(Item item, Item target) => true; /// /// Overridable. Virtual event invoked when the Mobile attempts to drop into another /// , . The target item is most likely a . /// /// True if the drop is allowed, false if otherwise. public virtual bool OnDroppedItemToItem(Item item, Item target, Point3D loc) => true; /// /// Overridable. Virtual event invoked when the Mobile attempts to give to a Mobile ( /// ). /// /// True if the drop is allowed, false if otherwise. public virtual bool OnDroppedItemToMobile(Item item, Mobile target) => true; /// /// Overridable. Virtual event invoked when the Mobile attempts to drop to the world at a /// /// /// /// . /// /// True if the drop is allowed, false if otherwise. public virtual bool OnDroppedItemToWorld(Item item, Point3D location) => true; /// /// Overridable. Virtual event when successfully uses while it's on this /// Mobile. /// /// public virtual void OnItemUsed(Mobile from, Item item) { } public virtual bool CheckNonlocalDrop(Mobile from, Item item, Item target) => from == this || (from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster); public virtual bool CheckItemUse(Mobile from, Item item) => true; /// /// Overridable. Virtual event invoked when successfully lifts from this /// Mobile. /// /// public virtual void OnItemLifted(Mobile from, Item item) { } public virtual bool AllowItemUse(Item item) => true; public virtual bool AllowEquipFrom(Mobile mob) => mob == this || (mob.AccessLevel >= AccessLevel.GameMaster && mob.AccessLevel > AccessLevel); public virtual bool EquipItem(Item item) { if (item?.Deleted != false || !item.CanEquip(this)) return false; if (CheckEquip(item) && OnEquip(item) && item.OnEquip(this)) { if (m_Spell?.OnCasterEquipping(item) == false) return false; // if (m_Spell != null && m_Spell.State == SpellState.Casting) // m_Spell.Disturb( DisturbType.EquipRequest ); AddItem(item); return true; } return false; } public void DefaultMobileInit() { m_StatCap = 225; m_FollowersMax = 5; Skills = new Skills(this); Items = new List(); StatMods = new List(); SkillMods = new List(); Map = Map.Internal; AutoPageNotify = true; Aggressors = new List(); Aggressed = new List(); Virtues = new VirtueInfo(); Stabled = new List(); DamageEntries = new List(); NextSkillTime = Core.TickCount; CreationTime = DateTime.UtcNow; } public virtual void Delta(MobileDelta flag) { if (m_Map == null || m_Map == Map.Internal || Deleted) return; m_DeltaFlags |= flag; if (!m_InDeltaQueue) { m_InDeltaQueue = true; if (_processing) lock (m_DeltaQueueR) { m_DeltaQueueR.Enqueue(this); try { using (var op = new StreamWriter("delta-recursion.log", true)) { op.WriteLine("# {0}", DateTime.UtcNow); op.WriteLine(new StackTrace()); op.WriteLine(); } } catch { // ignored } } else m_DeltaQueue.Enqueue(this); } Core.Set(); } public static void ProcessDeltaQueue() { _processing = true; if (m_DeltaQueue.Count >= 512) { Parallel.ForEach(m_DeltaQueue, m => m.ProcessDelta()); m_DeltaQueue.Clear(); } else { while (m_DeltaQueue.TryDequeue(out var m)) m.ProcessDelta(); } _processing = false; while (m_DeltaQueueR.TryDequeue(out var m)) m.ProcessDelta(); } public virtual void OnKillsChange(int oldValue) { } public bool CheckAlive(bool message = true) { if (Alive) return true; if (message) LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. return false; } public void LaunchBrowser(string url) { m_NetState?.LaunchBrowser(url); } public void InitStats(int str, int dex, int intel) { m_Str = str; m_Dex = dex; m_Int = intel; Hits = HitsMax; Stam = StamMax; Mana = ManaMax; Delta(MobileDelta.Stat | MobileDelta.Hits | MobileDelta.Stam | MobileDelta.Mana); } public virtual void DisplayPaperdollTo(Mobile to) { EventSink.InvokePaperdollRequest(to, this); } /// /// Overridable. Event invoked when the Mobile requests to open his own paperdoll via the 'Open Paperdoll' macro. /// public virtual void OnPaperdollRequest() { if (CanPaperdollBeOpenedBy(this)) DisplayPaperdollTo(this); } /// /// Overridable. Event invoked when wants to see this Mobile's stats. /// /// public virtual void OnStatsQuery(Mobile from) { if (from.Map == Map && Utility.InUpdateRange(this, from) && from.CanSee(this)) from.Send(new MobileStatus(from, this, m_NetState)); if (from == this) Send(new StatLockInfo(this)); if (Party is IParty ip) ip.OnStatsQuery(from, this); } /// /// Overridable. Event invoked when wants to see this Mobile's skills. /// public virtual void OnSkillsQuery(Mobile from) { if (from == this) Send(new SkillUpdate(Skills)); } /// /// Overridable. Virtual event invoked when changes. /// public virtual void OnRegionChange(Region old, Region @new) { } /// /// Overridable. Event invoked when the Mobile is single clicked. /// public virtual void OnSingleClick(Mobile from) { if (Deleted || (AccessLevel == AccessLevel.Player && DisableHiddenSelfClick && Hidden && from == this)) return; if (GuildClickMessage) { var guild = m_Guild; if (guild != null && (m_DisplayGuildTitle || (m_Player && guild.Type != GuildType.Regular))) { var title = GuildTitle?.Trim() ?? ""; string type; if (guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length) type = m_GuildTypes[(int)guild.Type]; else type = ""; var text = string.Format(title.Length <= 0 ? "[{1}]{2}" : "[{0}, {1}]{2}", title, guild.Abbreviation, type); PrivateOverheadMessage(MessageType.Regular, SpeechHue, true, text, from.NetState); } } int hue; if (NameHue != -1) hue = NameHue; else if (AccessLevel > AccessLevel.Player) hue = 11; else hue = Notoriety.GetHue(Notoriety.Compute(from, this)); var name = Name ?? string.Empty; var prefix = ""; if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) prefix = m_Female ? "Lady" : "Lord"; var suffix = ""; if (ClickTitle && !string.IsNullOrEmpty(Title)) suffix = Title; suffix = ApplyNameSuffix(suffix); string val; if (prefix.Length > 0 && suffix.Length > 0) val = $"{prefix} {name} {suffix}"; else if (prefix.Length > 0) val = $"{prefix} {name}"; else if (suffix.Length > 0) val = $"{name} {suffix}"; else val = name; PrivateOverheadMessage(MessageType.Label, hue, AsciiClickMessage, val, from.NetState); } public bool CheckSkill(SkillName skill, double minSkill, double maxSkill) => SkillCheckLocationHandler?.Invoke(this, skill, minSkill, maxSkill) == true; public bool CheckSkill(SkillName skill, double chance) => SkillCheckDirectLocationHandler?.Invoke(this, skill, chance) == true; public bool CheckTargetSkill(SkillName skill, object target, double minSkill, double maxSkill) => SkillCheckTargetHandler?.Invoke(this, skill, target, minSkill, maxSkill) == true; public bool CheckTargetSkill(SkillName skill, object target, double chance) => SkillCheckDirectTargetHandler?.Invoke(this, skill, target, chance) == true; public virtual void DisruptiveAction() { if (Meditating) { Meditating = false; SendLocalizedMessage(500134); // You stop meditating. } } /// /// Overridable. Virtual event invoked when the sector this Mobile is in gets activated. /// public virtual void OnSectorActivate() { } /// /// Overridable. Virtual event invoked when the sector this Mobile is in gets deactivated. /// public virtual void OnSectorDeactivate() { } private class MovementRecord { private static readonly Queue m_InstancePool = new Queue(); public long m_End; private MovementRecord(long end) => m_End = end; public static MovementRecord NewInstance(long end) { MovementRecord r; if (m_InstancePool.Count > 0) { r = m_InstancePool.Dequeue(); r.m_End = end; } else { r = new MovementRecord(end); } return r; } public bool Expired() { var v = Core.TickCount - m_End >= 0; if (v) m_InstancePool.Enqueue(this); return v; } } private class WarmodeTimer : Timer { private readonly Mobile m_Mobile; public WarmodeTimer(Mobile m, bool value) : base(WarmodeSpamDelay) { m_Mobile = m; Value = value; } public bool Value { get; set; } protected override void OnTick() { m_Mobile.Warmode = Value; m_Mobile.m_WarmodeChanges = 0; m_Mobile.m_WarmodeTimer = null; } } private class SimpleTarget : Target { private readonly TargetCallback m_Callback; public SimpleTarget(int range, TargetFlags flags, bool allowGround, TargetCallback callback) : base(range, allowGround, flags) => m_Callback = callback; protected override void OnTarget(Mobile from, object targeted) { m_Callback?.Invoke(from, targeted); } } private class SimpleStateTarget : Target { private readonly TargetStateCallback m_Callback; private readonly T m_State; public SimpleStateTarget(int range, TargetFlags flags, bool allowGround, TargetStateCallback callback, T state) : base(range, allowGround, flags) { m_Callback = callback; m_State = state; } protected override void OnTarget(Mobile from, object targeted) { m_Callback?.Invoke(from, targeted, m_State); } } private class AutoManifestTimer : Timer { private readonly Mobile m_Mobile; public AutoManifestTimer(Mobile m, TimeSpan delay) : base(delay) => m_Mobile = m; protected override void OnTick() { if (!m_Mobile.Alive) m_Mobile.Warmode = false; } } private class LocationComparer : IComparer { private static LocationComparer m_Instance; public LocationComparer(IEntity relativeTo) => RelativeTo = relativeTo; public IEntity RelativeTo { get; set; } public int Compare(IEntity x, IEntity y) => GetDistance(x) - GetDistance(y); public static LocationComparer GetInstance(IEntity relativeTo) { if (m_Instance == null) m_Instance = new LocationComparer(relativeTo); else m_Instance.RelativeTo = relativeTo; return m_Instance; } private int GetDistance(IEntity p) { var x = RelativeTo.X - p.X; var y = RelativeTo.Y - p.Y; var z = RelativeTo.Z - p.Z; x *= 11; y *= 11; return x * x + y * y + z * z; } } int IComparable.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); public int CompareTo(Mobile other) => other == null ? -1 : Serial.CompareTo(other.Serial); public static AllowBeneficialHandler AllowBeneficialHandler { get; set; } public static AllowHarmfulHandler AllowHarmfulHandler { get; set; } public static SkillCheckTargetHandler SkillCheckTargetHandler { get; set; } public static SkillCheckLocationHandler SkillCheckLocationHandler { get; set; } public static SkillCheckDirectTargetHandler SkillCheckDirectTargetHandler { get; set; } public static SkillCheckDirectLocationHandler SkillCheckDirectLocationHandler { get; set; } public static AOSStatusHandler AOSStatusHandler { get; set; } public static RegenRateHandler HitsRegenRateHandler { get; set; } public static TimeSpan DefaultHitsRate { get; set; } public static RegenRateHandler StamRegenRateHandler { get; set; } public static TimeSpan DefaultStamRate { get; set; } public static RegenRateHandler ManaRegenRateHandler { get; set; } public static TimeSpan DefaultManaRate { get; set; } public static TimeSpan GetHitsRegenRate(Mobile m) { if (HitsRegenRateHandler == null) return DefaultHitsRate; return HitsRegenRateHandler(m); } public static TimeSpan GetStamRegenRate(Mobile m) { if (StamRegenRateHandler == null) return DefaultStamRate; return StamRegenRateHandler(m); } public static TimeSpan GetManaRegenRate(Mobile m) { if (ManaRegenRateHandler == null) return DefaultManaRate; return ManaRegenRateHandler(m); } private Map m_Map; private Point3D m_Location; private Direction m_Direction; private Body m_Body; private int m_Hue; private Poison m_Poison; private BaseGuild m_Guild; private string m_GuildTitle; private bool m_Criminal; private string m_Name; private int m_Kills, m_ShortTermMurders; private string m_Language; private NetState m_NetState; private bool m_Female, m_Warmode, m_Hidden, m_Blessed, m_Flying; private int m_StatCap; private int m_Str, m_Dex, m_Int; private int m_Hits, m_Stam, m_Mana; private int m_Fame, m_Karma; private AccessLevel m_AccessLevel; private bool m_Player; private string m_Title; private int m_LightLevel; private int m_TotalGold, m_TotalItems, m_TotalWeight; private ISpell m_Spell; private Target m_Target; private Prompt m_Prompt; private ContextMenu m_ContextMenu; private Mobile m_Combatant; private bool m_CanHearGhosts; private int m_TithingPoints; private bool m_DisplayGuildTitle; private Timer m_ExpireCombatant; private Timer m_ExpireCriminal; private Timer m_ExpireAggrTimer; private Timer m_LogoutTimer; private Timer m_CombatTimer; private Timer m_ManaTimer, m_HitsTimer, m_StamTimer; private bool m_Paralyzed; private ParalyzedTimer m_ParaTimer; private bool m_Frozen; private FrozenTimer m_FrozenTimer; private int m_Hunger; private Region m_Region; private int m_VirtualArmor; private int m_Followers, m_FollowersMax; private List _actions; private Queue m_MoveRecords; private int m_WarmodeChanges; private DateTime m_NextWarmodeChange; private WarmodeTimer m_WarmodeTimer; private int m_VirtualArmorMod; private Body m_BodyMod; private Race m_Race; private class ManaTimer : Timer { private readonly Mobile m_Owner; public ManaTimer(Mobile m) : base(GetManaRegenRate(m), GetManaRegenRate(m)) { Priority = TimerPriority.FiftyMS; m_Owner = m; } protected override void OnTick() { if (m_Owner.CanRegenMana) m_Owner.Mana++; Delay = Interval = GetManaRegenRate(m_Owner); } } private class HitsTimer : Timer { private readonly Mobile m_Owner; public HitsTimer(Mobile m) : base(GetHitsRegenRate(m), GetHitsRegenRate(m)) { Priority = TimerPriority.FiftyMS; m_Owner = m; } protected override void OnTick() { if (m_Owner.CanRegenHits) m_Owner.Hits++; Delay = Interval = GetHitsRegenRate(m_Owner); } } private class StamTimer : Timer { private readonly Mobile m_Owner; public StamTimer(Mobile m) : base(GetStamRegenRate(m), GetStamRegenRate(m)) { Priority = TimerPriority.FiftyMS; m_Owner = m; } protected override void OnTick() { if (m_Owner.CanRegenStam) m_Owner.Stam++; Delay = Interval = GetStamRegenRate(m_Owner); } } private class LogoutTimer : Timer { private readonly Mobile m_Mobile; public LogoutTimer(Mobile m) : base(TimeSpan.FromDays(1.0)) { Priority = TimerPriority.OneSecond; m_Mobile = m; } protected override void OnTick() { if (m_Mobile.m_Map != Map.Internal) { EventSink.InvokeLogout(m_Mobile); m_Mobile.LogoutLocation = m_Mobile.m_Location; m_Mobile.LogoutMap = m_Mobile.m_Map; m_Mobile.Internalize(); } } } private class ParalyzedTimer : Timer { private readonly Mobile m_Mobile; public ParalyzedTimer(Mobile m, TimeSpan duration) : base(duration) { Priority = TimerPriority.TwentyFiveMS; m_Mobile = m; } protected override void OnTick() { m_Mobile.Paralyzed = false; } } private class FrozenTimer : Timer { private readonly Mobile m_Mobile; public FrozenTimer(Mobile m, TimeSpan duration) : base(duration) { Priority = TimerPriority.TwentyFiveMS; m_Mobile = m; } protected override void OnTick() { m_Mobile.Frozen = false; } } private class CombatTimer : Timer { private readonly Mobile m_Mobile; public CombatTimer(Mobile m) : base(TimeSpan.FromSeconds(0.0), TimeSpan.FromSeconds(0.01)) { m_Mobile = m; if (!m_Mobile.m_Player && m_Mobile.m_Dex <= 100) Priority = TimerPriority.FiftyMS; } protected override void OnTick() { if (Core.TickCount - m_Mobile.NextCombatTime < 0) return; var combatant = m_Mobile.Combatant; // If no combatant, wrong map, one of us is a ghost, or cannot see, or deleted, then stop combat if (combatant?.Deleted != false || m_Mobile.Deleted || combatant.m_Map != m_Mobile.m_Map || !combatant.Alive || !m_Mobile.Alive || !m_Mobile.CanSee(combatant) || combatant.IsDeadBondedPet || m_Mobile.IsDeadBondedPet) { m_Mobile.Combatant = null; return; } var weapon = m_Mobile.Weapon; if (!m_Mobile.InRange(combatant, weapon.MaxRange)) return; if (m_Mobile.InLOS(combatant)) { weapon.OnBeforeSwing(m_Mobile, combatant); // OnBeforeSwing for checking in regards to being hidden and whatnot m_Mobile.RevealingAction(); m_Mobile.NextCombatTime = Core.TickCount + (int)weapon.OnSwing(m_Mobile, combatant).TotalMilliseconds; } } } private class ExpireCombatantTimer : Timer { private readonly Mobile m_Mobile; public ExpireCombatantTimer(Mobile m) : base(TimeSpan.FromMinutes(1.0)) { Priority = TimerPriority.FiveSeconds; m_Mobile = m; } protected override void OnTick() { m_Mobile.Combatant = null; } } public static TimeSpan ExpireCriminalDelay { get; set; } = TimeSpan.FromMinutes(2.0); private class ExpireCriminalTimer : Timer { private readonly Mobile m_Mobile; public ExpireCriminalTimer(Mobile m) : base(ExpireCriminalDelay) { Priority = TimerPriority.FiveSeconds; m_Mobile = m; } protected override void OnTick() { m_Mobile.Criminal = false; } } private class ExpireAggressorsTimer : Timer { private readonly Mobile m_Mobile; public ExpireAggressorsTimer(Mobile m) : base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(5.0)) { m_Mobile = m; Priority = TimerPriority.FiveSeconds; } protected override void OnTick() { if (m_Mobile.Deleted || (m_Mobile.Aggressors.Count == 0 && m_Mobile.Aggressed.Count == 0)) m_Mobile.StopAggrExpire(); else m_Mobile.CheckAggrExpire(); } } private class SimplePrompt : Prompt { private readonly PromptCallback m_Callback; private readonly bool m_CallbackHandlesCancel; private readonly PromptCallback m_CancelCallback; public SimplePrompt(PromptCallback callback, PromptCallback cancelCallback) { m_Callback = callback; m_CancelCallback = cancelCallback; } public SimplePrompt(PromptCallback callback, bool callbackHandlesCancel = false) { m_Callback = callback; m_CallbackHandlesCancel = callbackHandlesCancel; } public override void OnResponse(Mobile from, string text) { m_Callback?.Invoke(from, text); } public override void OnCancel(Mobile from) { if (m_CallbackHandlesCancel && m_Callback != null) m_Callback(from, ""); else m_CancelCallback?.Invoke(from, ""); } } public Prompt BeginPrompt(PromptCallback callback, PromptCallback cancelCallback) { return Prompt = new SimplePrompt(callback, cancelCallback); } public Prompt BeginPrompt(PromptCallback callback, bool callbackHandlesCancel = false) { return Prompt = new SimplePrompt(callback, callbackHandlesCancel); } private class SimpleStatePrompt : Prompt { private readonly PromptStateCallback m_Callback; private readonly PromptStateCallback m_CancelCallback; private readonly T m_State; public SimpleStatePrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) { m_Callback = callback; m_CancelCallback = cancelCallback; m_State = state; } public SimpleStatePrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) { m_Callback = callback; m_State = state; m_CancelCallback = callbackHandlesCancel ? callback : null; } public SimpleStatePrompt(PromptStateCallback callback, T state) : this(callback, false, state) { } public override void OnResponse(Mobile from, string text) { m_Callback?.Invoke(from, text, m_State); } public override void OnCancel(Mobile from) { m_CancelCallback?.Invoke(from, "", m_State); } } public Prompt BeginPrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) => Prompt = new SimpleStatePrompt(callback, cancelCallback, state); public Prompt BeginPrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) => Prompt = new SimpleStatePrompt(callback, callbackHandlesCancel, state); public Prompt BeginPrompt(PromptStateCallback callback, T state) => BeginPrompt(callback, false, state); public Prompt Prompt { get => m_Prompt; set { var oldPrompt = m_Prompt; var newPrompt = value; if (oldPrompt == newPrompt) return; m_Prompt = null; if (newPrompt != null) oldPrompt?.OnCancel(this); m_Prompt = newPrompt; if (newPrompt != null) Send(new UnicodePrompt(newPrompt)); } } public virtual int GetAngerSound() { if (BaseSoundID != 0) return BaseSoundID; return -1; } public virtual int GetIdleSound() { if (BaseSoundID != 0) return BaseSoundID + 1; return -1; } public virtual int GetAttackSound() { if (BaseSoundID != 0) return BaseSoundID + 2; return -1; } public virtual int GetHurtSound() { if (BaseSoundID != 0) return BaseSoundID + 3; return -1; } public virtual int GetDeathSound() { if (BaseSoundID != 0) return BaseSoundID + 4; if (m_Body.IsHuman) return Utility.Random(m_Female ? 0x314 : 0x423, m_Female ? 4 : 5); return -1; } public IPooledEnumerable GetItemsInRange(int range) => GetItemsInRange(range); public IPooledEnumerable GetItemsInRange(int range) where T : Item { var map = m_Map; if (map == null) return Map.NullEnumerable.Instance; return map.GetItemsInRange(m_Location, range); } public IPooledEnumerable GetObjectsInRange(int range) { var map = m_Map; if (map == null) return Map.NullEnumerable.Instance; return map.GetObjectsInRange(m_Location, range); } public IPooledEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); public IPooledEnumerable GetMobilesInRange(int range) where T : Mobile { var map = m_Map; if (map == null) return Map.NullEnumerable.Instance; return map.GetMobilesInRange(m_Location, range); } public IPooledEnumerable GetClientsInRange(int range) { var map = m_Map; if (map == null) return Map.NullEnumerable.Instance; return map.GetClientsInRange(m_Location, range); } public void SayTo(Mobile to, bool ascii, string text) { PrivateOverheadMessage(MessageType.Regular, SpeechHue, ascii, text, to.NetState); } public void SayTo(Mobile to, string text) { SayTo(to, false, text); } public void SayTo(Mobile to, string format, params object[] args) { SayTo(to, false, string.Format(format, args)); } public void SayTo(Mobile to, bool ascii, string format, params object[] args) { SayTo(to, ascii, string.Format(format, args)); } public void SayTo(Mobile to, int number) { to.Send(new MessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, "")); } public void SayTo(Mobile to, int number, string args) { to.Send(new MessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, args)); } public void Say(bool ascii, string text) { PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text); } public void Say(string text) { PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text); } public void Say(string format, params object[] args) { Say(string.Format(format, args)); } public void Say(int number, AffixType type, string affix, string args) { PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args); } public void Say(int number, string args = "") { PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args); } public void Emote(string text) { PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text); } public void Emote(string format, params object[] args) { Emote(string.Format(format, args)); } public void Emote(int number, string args = "") { PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args); } public void Whisper(string text) { PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text); } public void Whisper(string format, params object[] args) { Whisper(string.Format(format, args)); } public void Whisper(int number, string args = "") { PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args); } public void Yell(string text) { PublicOverheadMessage(MessageType.Yell, YellHue, false, text); } public void Yell(string format, params object[] args) { Yell(string.Format(format, args)); } public void Yell(int number, string args = "") { PublicOverheadMessage(MessageType.Yell, YellHue, number, args); } public bool SendHuePicker(HuePicker p, bool throwOnOffline = false) { if (m_NetState != null) { p.SendTo(m_NetState); return true; } if (throwOnOffline) throw new MobileNotConnectedException(this, "Hue picker could not be sent."); return false; } public Gump FindGump() where T : Gump { return m_NetState?.Gumps.Find(g => g is T); } public bool CloseGump() where T : Gump { if (m_NetState == null) return false; var gump = FindGump(); if (gump != null) { // TODO: Recycle CloseGump m_NetState.Send(new CloseGump(gump.TypeID, 0)); m_NetState.RemoveGump(gump); gump.OnServerClose(m_NetState); } return true; } public bool CloseAllGumps() { var ns = m_NetState; if (ns == null) return false; var gumps = new List(ns.Gumps); ns.ClearGumps(); foreach (var gump in gumps) { ns.Send(new CloseGump(gump.TypeID, 0)); gump.OnServerClose(ns); } return true; } public bool HasGump() where T : Gump => FindGump() != null; public bool SendGump(Gump g) { if (m_NetState == null) return false; g.SendTo(m_NetState); return true; } public bool SendMenu(IMenu m) { if (m_NetState == null) return false; m.SendTo(m_NetState); return true; } public virtual bool CanBeBeneficial(Mobile target) => CanBeBeneficial(target, true, false); public virtual bool CanBeBeneficial(Mobile target, bool message) => CanBeBeneficial(target, message, false); public virtual bool CanBeBeneficial(Mobile target, bool message, bool allowDead) { if (target == null) return false; if (Deleted || target.Deleted || !Alive || IsDeadBondedPet || (!allowDead && (!target.Alive || target.IsDeadBondedPet))) { if (message) SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. return false; } if (target == this) return true; if (/*m_Player &&*/!Region.AllowBeneficial(this, target)) { // TODO: Pets // if (!(target.m_Player || target.Body.IsHuman || target.Body.IsAnimal)) // { if (message) SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. return false; // } } return true; } public virtual bool IsBeneficialCriminal(Mobile target) { if (this == target) return false; var n = Notoriety.Compute(this, target); return n == Notoriety.Criminal || n == Notoriety.Murderer; } /// /// Overridable. Event invoked when the Mobile does a beneficial action. /// public virtual void OnBeneficialAction(Mobile target, bool isCriminal) { if (isCriminal) CriminalAction(false); } public virtual void DoBeneficial(Mobile target) { if (target == null) return; OnBeneficialAction(target, IsBeneficialCriminal(target)); Region.OnBeneficialAction(this, target); target.Region.OnGotBeneficialAction(this, target); } public virtual bool BeneficialCheck(Mobile target) { if (CanBeBeneficial(target, true)) { DoBeneficial(target); return true; } return false; } public virtual bool CanBeHarmful(Mobile target) => CanBeHarmful(target, true); public virtual bool CanBeHarmful(Mobile target, bool message) => CanBeHarmful(target, message, false); public virtual bool CanBeHarmful(Mobile target, bool message, bool ignoreOurBlessedness) { if (target == null) return false; if (Deleted || (!ignoreOurBlessedness && m_Blessed) || target.Deleted || target.m_Blessed || !Alive || IsDeadBondedPet || !target.Alive || target.IsDeadBondedPet) { if (message) SendLocalizedMessage(1001018); // You can not perform negative acts on your target. return false; } if (target == this) return true; // TODO: Pets if (/*m_Player &&*/ !Region.AllowHarmful(this, target)) // (target.m_Player || target.Body.IsHuman) && !Region.AllowHarmful( this, target ) ) { if (message) SendLocalizedMessage(1001018); // You can not perform negative acts on your target. return false; } return true; } public virtual bool IsHarmfulCriminal(Mobile target) => this != target && Notoriety.Compute(this, target) == Notoriety.Innocent; /// /// Overridable. Event invoked when the Mobile does a harmful action. /// public virtual void OnHarmfulAction(Mobile target, bool isCriminal) { if (isCriminal) CriminalAction(false); } public virtual void DoHarmful(Mobile target) { DoHarmful(target, false); } public virtual void DoHarmful(Mobile target, bool indirect) { if (target == null || Deleted) return; var isCriminal = IsHarmfulCriminal(target); OnHarmfulAction(target, isCriminal); target.AggressiveAction(this, isCriminal); Region.OnDidHarmful(this, target); target.Region.OnGotHarmful(this, target); if (!indirect) Combatant = target; if (m_ExpireCombatant == null) m_ExpireCombatant = new ExpireCombatantTimer(this); else m_ExpireCombatant.Stop(); m_ExpireCombatant.Start(); } public virtual bool HarmfulCheck(Mobile target) { if (CanBeHarmful(target)) { DoHarmful(target); return true; } return false; } /// /// Gets a list of all StatMod's currently active for the Mobile. /// public List StatMods { get; private set; } public bool RemoveStatMod(string name) { for (var i = 0; i < StatMods.Count; ++i) { var check = StatMods[i]; if (check.Name == name) { StatMods.RemoveAt(i); CheckStatTimers(); Delta(MobileDelta.Stat | GetStatDelta(check.Type)); return true; } } return false; } public StatMod GetStatMod(string name) { for (var i = 0; i < StatMods.Count; ++i) { var check = StatMods[i]; if (check.Name == name) return check; } return null; } public void AddStatMod(StatMod mod) { for (var i = 0; i < StatMods.Count; ++i) { var check = StatMods[i]; if (check.Name == mod.Name) { Delta(MobileDelta.Stat | GetStatDelta(check.Type)); StatMods.RemoveAt(i); break; } } StatMods.Add(mod); Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); CheckStatTimers(); } private MobileDelta GetStatDelta(StatType type) { MobileDelta delta = 0; if ((type & StatType.Str) != 0) delta |= MobileDelta.Hits; if ((type & StatType.Dex) != 0) delta |= MobileDelta.Stam; if ((type & StatType.Int) != 0) delta |= MobileDelta.Mana; return delta; } /// /// Computes the total modified offset for the specified stat type. Expired instances are removed. /// public int GetStatOffset(StatType type) { var offset = 0; for (var i = 0; i < StatMods.Count; ++i) { var mod = StatMods[i]; if (mod.HasElapsed()) { StatMods.RemoveAt(i); Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); CheckStatTimers(); --i; } else if ((mod.Type & type) != 0) { offset += mod.Offset; } } return offset; } /// /// Overridable. Virtual event invoked when the changes. /// /// /// public virtual void OnRawStrChange(int oldValue) { } /// /// Overridable. Virtual event invoked when changes. /// /// /// public virtual void OnRawDexChange(int oldValue) { } /// /// Overridable. Virtual event invoked when the changes. /// /// /// public virtual void OnRawIntChange(int oldValue) { } /// /// Overridable. Virtual event invoked when the , , or /// changes. /// /// /// /// public virtual void OnRawStatChange(StatType stat, int oldValue) { } /// /// Gets or sets the base, unmodified, strength of the Mobile. Ranges from 1 to 65000, inclusive. /// /// /// /// /// [CommandProperty(AccessLevel.GameMaster)] public int RawStr { get => m_Str; set { if (value < 1) value = 1; else if (value > 65000) value = 65000; if (m_Str != value) { var oldValue = m_Str; m_Str = value; Delta(MobileDelta.Stat | MobileDelta.Hits); if (Hits < HitsMax) { m_HitsTimer ??= new HitsTimer(this); m_HitsTimer.Start(); } else if (Hits > HitsMax) { Hits = HitsMax; } OnRawStrChange(oldValue); OnRawStatChange(StatType.Str, oldValue); } } } /// /// Gets or sets the effective strength of the Mobile. This is the sum of the plus any additional /// modifiers. Any attempts to set this value when under the influence of a will result in no change. /// It ranges from 1 to 65000, inclusive. /// /// /// [CommandProperty(AccessLevel.GameMaster)] public virtual int Str { get { var value = m_Str + GetStatOffset(StatType.Str); if (value < 1) value = 1; else if (value > 65000) value = 65000; return value; } set { if (StatMods.Count == 0) RawStr = value; } } /// /// Gets or sets the base, unmodified, dexterity of the Mobile. Ranges from 1 to 65000, inclusive. /// /// /// /// /// [CommandProperty(AccessLevel.GameMaster)] public int RawDex { get => m_Dex; set { if (value < 1) value = 1; else if (value > 65000) value = 65000; if (m_Dex != value) { var oldValue = m_Dex; m_Dex = value; Delta(MobileDelta.Stat | MobileDelta.Stam); if (Stam < StamMax) { m_StamTimer ??= new StamTimer(this); m_StamTimer.Start(); } else if (Stam > StamMax) { Stam = StamMax; } OnRawDexChange(oldValue); OnRawStatChange(StatType.Dex, oldValue); } } } /// /// Gets or sets the effective dexterity of the Mobile. This is the sum of the plus any additional /// modifiers. Any attempts to set this value when under the influence of a will result in no change. /// It ranges from 1 to 65000, inclusive. /// /// /// [CommandProperty(AccessLevel.GameMaster)] public virtual int Dex { get { var value = m_Dex + GetStatOffset(StatType.Dex); if (value < 1) value = 1; else if (value > 65000) value = 65000; return value; } set { if (StatMods.Count == 0) RawDex = value; } } /// /// Gets or sets the base, unmodified, intelligence of the Mobile. Ranges from 1 to 65000, inclusive. /// /// /// /// /// [CommandProperty(AccessLevel.GameMaster)] public int RawInt { get => m_Int; set { if (value < 1) value = 1; else if (value > 65000) value = 65000; if (m_Int != value) { var oldValue = m_Int; m_Int = value; Delta(MobileDelta.Stat | MobileDelta.Mana); if (Mana < ManaMax) { m_ManaTimer ??= new ManaTimer(this); m_ManaTimer.Start(); } else if (Mana > ManaMax) { Mana = ManaMax; } OnRawIntChange(oldValue); OnRawStatChange(StatType.Int, oldValue); } } } /// /// Gets or sets the effective intelligence of the Mobile. This is the sum of the plus any additional /// modifiers. Any attempts to set this value when under the influence of a will result in no change. /// It ranges from 1 to 65000, inclusive. /// /// /// [CommandProperty(AccessLevel.GameMaster)] public virtual int Int { get { var value = m_Int + GetStatOffset(StatType.Int); if (value < 1) value = 1; else if (value > 65000) value = 65000; return value; } set { if (StatMods.Count == 0) RawInt = value; } } public virtual void OnHitsChange(int oldValue) { } public virtual void OnStamChange(int oldValue) { } public virtual void OnManaChange(int oldValue) { } /// /// Gets or sets the current hit point of the Mobile. This value ranges from 0 to , inclusive. When set /// to the value of , the CanReportMurder flag of all /// aggressors is reset to false, and the list of damage entries is cleared. /// [CommandProperty(AccessLevel.GameMaster)] public int Hits { get => m_Hits; set { if (Deleted) return; if (value < 0) { value = 0; } else if (value >= HitsMax) { value = HitsMax; m_HitsTimer?.Stop(); for (var i = 0; i < Aggressors.Count; i++) // reset reports on full HP Aggressors[i].CanReportMurder = false; if (DamageEntries.Count > 0) DamageEntries.Clear(); // reset damage entries on full HP } if (value < HitsMax) { if (CanRegenHits) { m_HitsTimer ??= new HitsTimer(this); m_HitsTimer.Start(); } else { m_HitsTimer?.Stop(); } } if (m_Hits != value) { var oldValue = m_Hits; m_Hits = value; Delta(MobileDelta.Hits); OnHitsChange(oldValue); } } } /// /// Overridable. Gets the maximum hit point of the Mobile. By default, this returns: 50 + ( / 2) /// [CommandProperty(AccessLevel.GameMaster)] public virtual int HitsMax => 50 + Str / 2; /// /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. /// [CommandProperty(AccessLevel.GameMaster)] public int Stam { get => m_Stam; set { if (Deleted) return; if (value < 0) { value = 0; } else if (value >= StamMax) { value = StamMax; m_StamTimer?.Stop(); } if (value < StamMax) { if (CanRegenStam) { m_StamTimer ??= new StamTimer(this); m_StamTimer.Start(); } else { m_StamTimer?.Stop(); } } if (m_Stam != value) { var oldValue = m_Stam; m_Stam = value; Delta(MobileDelta.Stam); OnStamChange(oldValue); } } } /// /// Overridable. Gets the maximum stamina of the Mobile. By default, this returns: /// /// /// /// [CommandProperty(AccessLevel.GameMaster)] public virtual int StamMax => Dex; /// /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. /// [CommandProperty(AccessLevel.GameMaster)] public int Mana { get => m_Mana; set { if (Deleted) return; if (value < 0) { value = 0; } else if (value >= ManaMax) { value = ManaMax; m_ManaTimer?.Stop(); if (Meditating) { Meditating = false; SendLocalizedMessage(501846); // You are at peace. } } if (value < ManaMax) { if (CanRegenMana) { m_ManaTimer ??= new ManaTimer(this); m_ManaTimer.Start(); } else { m_ManaTimer?.Stop(); } } if (m_Mana != value) { var oldValue = m_Mana; m_Mana = value; Delta(MobileDelta.Mana); OnManaChange(oldValue); } } } /// /// Overridable. Gets the maximum mana of the Mobile. By default, this returns: /// /// /// /// [CommandProperty(AccessLevel.GameMaster)] public virtual int ManaMax => Int; public Timer PoisonTimer { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public Poison Poison { get => m_Poison; set { /*if (m_Poison != value && (m_Poison == null || value == null || m_Poison.Level < value.Level)) {*/ m_Poison = value; Delta(MobileDelta.HealthbarPoison); if (PoisonTimer != null) { PoisonTimer.Stop(); PoisonTimer = null; } if (m_Poison != null) { PoisonTimer = m_Poison.ConstructTimer(this); PoisonTimer?.Start(); } CheckStatTimers(); /*}*/ } } /// /// Overridable. Event invoked when a call to failed because /// returned false: the Mobile was resistant to the poison. By default, this broadcasts an overhead message: * The poison /// seems to have no effect. * /// /// /// /// public virtual void OnPoisonImmunity(Mobile from, Poison poison) { PublicOverheadMessage(MessageType.Emote, 0x3B2, 1005534); // * The poison seems to have no effect. * } /// /// Overridable. Virtual event invoked when a call to failed because /// returned false: the Mobile was already poisoned by an equal or greater strength poison. /// /// /// /// public virtual void OnHigherPoison(Mobile from, Poison poison) { } /// /// Overridable. Event invoked when a call to succeeded. By default, this broadcasts an overhead /// message varying by the level of the poison. Example: * Zippy begins to spasm uncontrollably. * /// /// /// public virtual void OnPoisoned(Mobile from, Poison poison, Poison oldPoison) { if (poison != null) { LocalOverheadMessage(MessageType.Regular, 0x21, 1042857 + poison.Level * 2); NonlocalOverheadMessage(MessageType.Regular, 0x21, 1042858 + poison.Level * 2, Name); } } /// /// Overridable. Called from , this method checks if the Mobile is immune to some /// . If true, will be invoked and /// is returned. /// /// /// /// public virtual bool CheckPoisonImmunity(Mobile from, Poison poison) => false; /// /// Overridable. Called from , this method checks if the Mobile is already poisoned by some /// of equal or greater strength. If true, will be invoked and /// is returned. /// /// /// /// public virtual bool CheckHigherPoison(Mobile from, Poison poison) => m_Poison != null && m_Poison.Level >= poison.Level; /// /// Overridable. Attempts to apply poison to the Mobile. Checks are made such that no /// higher poison is active and that the Mobile is not /// immune to the poison. Provided those assertions are true, the /// is applied and is invoked. /// /// /// /// /// One of four possible values: /// /// /// /// Cured /// /// The parameter was null and so was invoked. /// /// /// /// HigherPoisonActive /// /// The call to returned false. /// /// /// /// Immune /// /// The call to returned false. /// /// /// /// Poisoned /// /// The was successfully applied. /// /// /// public virtual ApplyPoisonResult ApplyPoison(Mobile from, Poison poison) { if (poison == null) { CurePoison(from); return ApplyPoisonResult.Cured; } if (CheckHigherPoison(from, poison)) { OnHigherPoison(from, poison); return ApplyPoisonResult.HigherPoisonActive; } if (CheckPoisonImmunity(from, poison)) { OnPoisonImmunity(from, poison); return ApplyPoisonResult.Immune; } var oldPoison = m_Poison; Poison = poison; OnPoisoned(from, poison, oldPoison); return ApplyPoisonResult.Poisoned; } /// /// Overridable. Called from , this method checks to see that the Mobile can be cured of /// /// /// /// public virtual bool CheckCure(Mobile from) => true; /// /// Overridable. Virtual event invoked when a call to succeeded. /// /// /// /// public virtual void OnCured(Mobile from, Poison oldPoison) { } /// /// Overridable. Virtual event invoked when a call to failed. /// /// /// /// public virtual void OnFailedCure(Mobile from) { } /// /// Overridable. Attempts to cure any poison that is currently active. /// /// True if poison was cured, false if otherwise. public virtual bool CurePoison(Mobile from) { if (CheckCure(from)) { var oldPoison = m_Poison; Poison = null; OnCured(from, oldPoison); return true; } OnFailedCure(from); return false; } private HairInfo m_Hair; private FacialHairInfo m_FacialHair; [CommandProperty(AccessLevel.GameMaster)] public int HairItemID { get => m_Hair?.ItemID ?? 0; set { if (m_Hair == null && value > 0) m_Hair = new HairInfo(value); else if (value <= 0) m_Hair = null; else if (m_Hair != null) m_Hair.ItemID = value; Delta(MobileDelta.Hair); } } // [CommandProperty( AccessLevel.GameMaster )] // public int HairSerial { get { return HairInfo.FakeSerial( this ); } } [CommandProperty(AccessLevel.GameMaster)] public int FacialHairItemID { get => m_FacialHair?.ItemID ?? 0; set { if (m_FacialHair == null && value > 0) m_FacialHair = new FacialHairInfo(value); else if (value <= 0) m_FacialHair = null; else if (m_FacialHair != null) m_FacialHair.ItemID = value; Delta(MobileDelta.FacialHair); } } // [CommandProperty( AccessLevel.GameMaster )] // public int FacialHairSerial { get { return FacialHairInfo.FakeSerial( this ); } } [CommandProperty(AccessLevel.GameMaster)] public int HairHue { get => m_Hair?.Hue ?? 0; set { if (m_Hair != null) { m_Hair.Hue = value; Delta(MobileDelta.Hair); } } } [CommandProperty(AccessLevel.GameMaster)] public int FacialHairHue { get => m_FacialHair?.Hue ?? 0; set { if (m_FacialHair != null) { m_FacialHair.Hue = value; Delta(MobileDelta.FacialHair); } } } public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, int hue, int renderMode) { Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode); } public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes) { Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes); } public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer, int unknown) { Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, explodeEffect, explodeSound, layer, unknown); } public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, int unknown) { Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, explodeEffect, explodeSound, (EffectLayer)255, unknown); } public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound, int unknown) { Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, effect, explodeEffect, explodeSound, unknown); } public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound) { Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, 0, 0, effect, explodeEffect, explodeSound, 0); } public void FixedEffect(int itemID, int speed, int duration, int hue, int renderMode) { Effects.SendTargetEffect(this, itemID, speed, duration, hue, renderMode); } public void FixedEffect(int itemID, int speed, int duration) { Effects.SendTargetEffect(this, itemID, speed, duration, 0, 0); } public void FixedParticles(int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer, int unknown) { Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, unknown); } public void FixedParticles(int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer) { Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, 0); } public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer, int unknown) { Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, unknown); } public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer) { Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, 0); } public void BoltEffect(int hue) { Effects.SendBoltEffect(this, true, hue); } public Direction GetDirectionTo(int x, int y) { var dx = m_Location.m_X - x; var dy = m_Location.m_Y - y; var rx = (dx - dy) * 44; var ry = (dx + dy) * 44; var ax = Math.Abs(rx); var ay = Math.Abs(ry); Direction ret; if ((ay >> 1) - ax >= 0) ret = ry > 0 ? Direction.Up : Direction.Down; else if ((ax >> 1) - ay >= 0) ret = rx > 0 ? Direction.Left : Direction.Right; else if (rx >= 0 && ry >= 0) ret = Direction.West; else if (rx >= 0 && ry < 0) ret = Direction.South; else if (rx < 0 && ry < 0) ret = Direction.East; else ret = Direction.North; return ret; } public Direction GetDirectionTo(Point2D p) => GetDirectionTo(p.m_X, p.m_Y); public Direction GetDirectionTo(Point3D p) => GetDirectionTo(p.m_X, p.m_Y); public Direction GetDirectionTo(IPoint2D p) { if (p == null) return Direction.North; return GetDirectionTo(p.X, p.Y); } public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true) { if (m_Map == null) return; var p = ascii ? (Packet)new AsciiMessage(Serial, Body, type, hue, 3, Name, text) : new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text); p.Acquire(); var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) state.Send(p); Packet.Release(p); eable.Free(); } public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "", bool noLineOfSight = true) { if (m_Map == null) return; var p = Packet.Acquire(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) state.Send(p); Packet.Release(p); eable.Free(); } public void PublicOverheadMessage(MessageType type, int hue, int number, AffixType affixType, string affix, string args = "", bool noLineOfSight = false) { if (m_Map == null) return; var p = Packet.Acquire(new MessageLocalizedAffix(Serial, Body, type, hue, 3, number, Name, affixType, affix, args)); var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) state.Send(p); Packet.Release(p); eable.Free(); } public void PrivateOverheadMessage(MessageType type, int hue, bool ascii, string text, NetState state) { if (state == null) return; if (ascii) state.Send(new AsciiMessage(Serial, Body, type, hue, 3, Name, text)); else state.Send(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); } public void PrivateOverheadMessage(MessageType type, int hue, int number, NetState state) { PrivateOverheadMessage(type, hue, number, "", state); } public void PrivateOverheadMessage(MessageType type, int hue, int number, string args, NetState state) { state?.Send(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); } public void LocalOverheadMessage(MessageType type, int hue, bool ascii, string text) { var ns = m_NetState; if (ns == null) return; if (ascii) ns.Send(new AsciiMessage(Serial, Body, type, hue, 3, Name, text)); else ns.Send(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); } public void LocalOverheadMessage(MessageType type, int hue, int number, string args = "") { m_NetState?.Send(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); } public void NonlocalOverheadMessage(MessageType type, int hue, int number, string args = "") { if (m_Map == null) return; var p = Packet.Acquire(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state != m_NetState && state.Mobile.CanSee(this)) state.Send(p); Packet.Release(p); eable.Free(); } public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, string text) { if (m_Map == null) return; var p = ascii ? (Packet)new AsciiMessage(Serial, Body, type, hue, 3, Name, text) : new UnicodeMessage(Serial, Body, type, hue, 3, Language, Name, text); p.Acquire(); var eable = m_Map.GetClientsInRange(m_Location); foreach (var state in eable) if (state != m_NetState && state.Mobile.CanSee(this)) state.Send(p); Packet.Release(p); eable.Free(); } public void SendLocalizedMessage(int number) { m_NetState?.Send(MessageLocalized.InstantiateGeneric(number)); } public void SendLocalizedMessage(int number, string args, int hue = 0x3B2) { if (hue == 0x3B2 && string.IsNullOrEmpty(args)) m_NetState?.Send(MessageLocalized.InstantiateGeneric(number)); else m_NetState?.Send(new MessageLocalized(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", args)); } public void SendLocalizedMessage(int number, bool append, string affix, string args = "", int hue = 0x3B2) { m_NetState?.Send(new MessageLocalizedAffix(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", (append ? AffixType.Append : AffixType.Prepend) | AffixType.System, affix, args)); } public void SendMessage(string text) { SendMessage(0x3B2, text); } public void SendMessage(string format, params object[] args) { SendMessage(0x3B2, string.Format(format, args)); } public void SendMessage(int hue, string text) { m_NetState?.Send(new UnicodeMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text)); } public void SendMessage(int hue, string format, params object[] args) { SendMessage(hue, string.Format(format, args)); } public void SendAsciiMessage(string text) { SendAsciiMessage(0x3B2, text); } public void SendAsciiMessage(string format, params object[] args) { SendAsciiMessage(0x3B2, string.Format(format, args)); } public void SendAsciiMessage(int hue, string text) { m_NetState?.Send(new AsciiMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "System", text)); } public void SendAsciiMessage(int hue, string format, params object[] args) { SendAsciiMessage(hue, string.Format(format, args)); } public virtual bool InRange(Point2D p, int range) => p.m_X >= Location.m_X - range && p.m_X <= Location.m_X + range && p.m_Y >= Location.m_Y - range && p.m_Y <= Location.m_Y + range; public virtual bool InRange(Point3D p, int range) => p.m_X >= Location.m_X - range && p.m_X <= Location.m_X + range && p.m_Y >= Location.m_Y - range && p.m_Y <= Location.m_Y + range; public virtual bool InRange(IPoint2D p, int range) => p.X >= Location.m_X - range && p.X <= Location.m_X + range && p.Y >= Location.m_Y - range && p.Y <= Location.m_Y + range; /// /// Overridable. Event invoked when the Mobile is double clicked. By default, this method can either dismount or open the /// paperdoll. /// /// /// public virtual void OnDoubleClick(Mobile from) { if (this == from && (!DisableDismountInWarmode || !m_Warmode)) { var mount = Mount; if (mount != null) { mount.Rider = null; return; } } if (CanPaperdollBeOpenedBy(from)) DisplayPaperdollTo(from); } /// /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who is over 18 tiles away. /// /// public virtual void OnDoubleClickOutOfRange(Mobile from) { } /// /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who can no longer see the Mobile. This may /// happen, for example, using 'Last Object' after the Mobile has hidden. /// /// public virtual void OnDoubleClickCantSee(Mobile from) { } /// /// Overridable. Event invoked when the Mobile is double clicked by someone who is not alive. Similar to /// , this method will show the paperdoll. It does not, however, provide any dismount /// functionality. /// /// public virtual void OnDoubleClickDead(Mobile from) { if (CanPaperdollBeOpenedBy(from)) DisplayPaperdollTo(from); } public Item ShieldArmor => FindItemOnLayer(Layer.TwoHanded); public Item NeckArmor => FindItemOnLayer(Layer.Neck); public Item HandArmor => FindItemOnLayer(Layer.Gloves); public Item HeadArmor => FindItemOnLayer(Layer.Helm); public Item ArmsArmor => FindItemOnLayer(Layer.Arms); public Item LegsArmor => FindItemOnLayer(Layer.InnerLegs) ?? FindItemOnLayer(Layer.Pants); public Item ChestArmor => FindItemOnLayer(Layer.InnerTorso) ?? FindItemOnLayer(Layer.Shirt); public Item Talisman => FindItemOnLayer(Layer.Talisman); } }