context: #region References
  using System;
  using System.Collections.Generic;
   
  using Server.Commands;
  using Server.Engines.Craft;
  using Server.Multis;
  using Server.Network;
  using Server.Spells;
  using Server.Targeting;
  using Server.Mobiles;
  using Server.Spells.Mysticism;
  #endregion
  
  namespace Server.Items
  {
  	public enum SpellbookType
  	{
  		Invalid = -1,
  		Regular,
  		Necromancer,
  		Paladin,
  		Ninja,
  		Samurai,
  		Arcanist,
  		Mystic,
          SkillMasteries
  	}
  
  	public enum BookQuality
  	{
  		Regular,
  		Exceptional,
  	}
  
      public class Spellbook : Item, ICraftable, ISlayer, IEngravable, IVvVItem, IOwnerRestricted, IWearableDurability
  	{
  		private static readonly Dictionary<Mobile, List<Spellbook>> m_Table = new Dictionary<Mobile, List<Spellbook>>();
  
  		private static readonly int[] m_LegendPropertyCounts = new[]
  		{
  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 properties : 21/52 : 40%
  			1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 property   : 15/52 : 29%
  			2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 2 properties : 10/52 : 19%
  			3, 3, 3, 3, 3, 3 // 3 properties :  6/52 : 12%
  		};
  
  		private static readonly int[] m_ElderPropertyCounts = new[]
  		{
  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 properties : 15/34 : 44%
  			1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 property   : 10/34 : 29%
  			2, 2, 2, 2, 2, 2, // 2 properties :  6/34 : 18%
  			3, 3, 3 // 3 properties :  3/34 :  9%
  		};
  
  		private static readonly int[] m_GrandPropertyCounts = new[]
  		{
  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 properties : 10/20 : 50%
  			1, 1, 1, 1, 1, 1, // 1 property   :  6/20 : 30%
  			2, 2, 2, // 2 properties :  3/20 : 15%
  			3 // 3 properties :  1/20 :  5%
  		};
  
  		private static readonly int[] m_MasterPropertyCounts = new[]
  		{
  			0, 0, 0, 0, 0, 0, // 0 properties : 6/10 : 60%
  			1, 1, 1, // 1 property   : 3/10 : 30%
  			2 // 2 properties : 1/10 : 10%
  		};
  
  		private static readonly int[] m_AdeptPropertyCounts = new[]
  		{
  			0, 0, 0, // 0 properties : 3/4 : 75%
  			1 // 1 property   : 1/4 : 25%
  		};
  
  		private string m_EngravedText;
  		private BookQuality m_Quality;
  		private AosAttributes m_AosAttributes;
  		private AosSkillBonuses m_AosSkillBonuses;
          private NegativeAttributes m_NegativeAttributes;
  		private ulong m_Content;
  		private int m_Count;
  		private Mobile m_Crafter;
  		private SlayerName m_Slayer;
  		private SlayerName m_Slayer2;
  		//Currently though there are no dual slayer spellbooks, OSI has a habit of putting dual slayer stuff in later
  		[Constructable]
  		public Spellbook()
  			: this((ulong)0)
  		{ }
  
  		[Constructable]
  		public Spellbook(ulong content)
  			: this(content, 0xEFA)
  		{ }
  
  		public Spellbook(ulong content, int itemID)
  			: base(itemID)
  		{
  			m_AosAttributes = new AosAttributes(this);
  			m_AosSkillBonuses = new AosSkillBonuses(this);
              m_NegativeAttributes = new NegativeAttributes(this);
  
  			Weight = 3.0;
  			Layer = Layer.OneHanded;
  			LootType = LootType.Blessed;
  
  			Content = content;
  		}
  
  		public Spellbook(Serial serial)
  			: base(serial)
  		{ }
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public string EngravedText
  		{
  			get { return m_EngravedText; }
  			set
  			{
  				m_EngravedText = value;
  				InvalidateProperties();
  			}
  		}
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public BookQuality Quality
  		{
  			get { return m_Quality; }
  			set
  			{
  				m_Quality = value;
  				InvalidateProperties();
  			}
  		}
  
  		public override bool DisplayWeight { get { return false; } }
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public AosAttributes Attributes { get { return m_AosAttributes; } set { } }
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public AosSkillBonuses SkillBonuses { get { return m_AosSkillBonuses; } set { } }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public NegativeAttributes NegativeAttributes { get { return m_NegativeAttributes; } set { } }
  
  		public virtual SpellbookType SpellbookType { get { return SpellbookType.Regular; } }
  		public virtual int BookOffset { get { return 0; } }
  		public virtual int BookCount { get { return 64; } }
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public ulong Content
  		{
  			get { return m_Content; }
  			set
  			{
  				if (m_Content != value)
  				{
  					m_Content = value;
  
  					m_Count = 0;
  
  					while (value > 0)
  					{
  						m_Count += (int)(value & 0x1);
  						value >>= 1;
  					}
  
  					InvalidateProperties();
  				}
  			}
  		}
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public int SpellCount { get { return m_Count; } }
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public Mobile Crafter
  		{
  			get { return m_Crafter; }
  			set
  			{
  				m_Crafter = value;
  				InvalidateProperties();
  			}
  		}
  
  		public override bool DisplayLootType { get { return Core.AOS; } }
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public SlayerName Slayer
  		{
  			get { return m_Slayer; }
  			set
  			{
  				m_Slayer = value;
  				InvalidateProperties();
  			}
  		}
  
  		[CommandProperty(AccessLevel.GameMaster)]
  		public SlayerName Slayer2
  		{
  			get { return m_Slayer2; }
  			set
  			{
  				m_Slayer2 = value;
  				InvalidateProperties();
  			}
  		}
  
          #region IVvVItem / IOwnerRestricted
          private bool _VvVItem;
          private Mobile _Owner;
          private string _OwnerName;
  
          [CommandProperty(AccessLevel.GameMaster)]
          public bool IsVvVItem
          {
              get { return _VvVItem; }
              set { _VvVItem = value; InvalidateProperties(); }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public Mobile Owner
          {
              get { return _Owner; }
              set { _Owner = value; if (_Owner != null) _OwnerName = _Owner.Name; InvalidateProperties(); }
          }
  
          public virtual string OwnerName
          {
              get { return _OwnerName; }
              set { _OwnerName = value; InvalidateProperties(); }
          }
          #endregion
  
          #region IWearableDurability
          private int m_MaxHitPoints;
          private int m_HitPoints;
  
          [CommandProperty(AccessLevel.GameMaster)]
          public int HitPoints
          {
              get { return m_HitPoints; }
              set
              {
                  if (m_HitPoints == value)
                  {
                      return;
                  }
  
                  if (value > m_MaxHitPoints)
                  {
                      value = m_MaxHitPoints;
                  }
  
                  m_HitPoints = value;
  
                  InvalidateProperties();
              }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public int MaxHitPoints
          {
              get { return m_MaxHitPoints; }
              set
              {
                  m_MaxHitPoints = value;
                  InvalidateProperties();
              }
          }
  
          public virtual bool CanFortify { get { return false; } }
  
          public virtual int InitMinHits { get { return 0; } }
          public virtual int InitMaxHits { get { return 0; } }
  
          public virtual void ScaleDurability()
          {
          }
  
          public virtual void UnscaleDurability()
          {
          }
  
          public virtual int OnHit(BaseWeapon weap, int damage)
          {
              if (m_MaxHitPoints == 0)
                  return damage;
  
              int chance = m_NegativeAttributes.Antique > 0 ? 50 : 25;
  
              if (chance > Utility.Random(100)) // 25% chance to lower durability
              {
                  if (m_HitPoints >= 1)
                  {
                      HitPoints--;
                  }
                  else if (m_MaxHitPoints > 0)
                  {
                      MaxHitPoints--;
  
                      if (Parent is Mobile)
                          ((Mobile)Parent).LocalOverheadMessage(MessageType.Regular, 0x3B2, 1061121); // Your equipment is severely damaged.
  
                      if (m_MaxHitPoints == 0)
                      {
                          Delete();
                      }
                  }
              }
  
              return damage;
          }
          #endregion
  
          public static void Initialize()
  		{
  			EventSink.OpenSpellbookRequest += EventSink_OpenSpellbookRequest;
  			EventSink.CastSpellRequest += EventSink_CastSpellRequest;
              EventSink.TargetedSpell += Targeted_Spell;       
  
  			CommandSystem.Register("AllSpells", AccessLevel.GameMaster, AllSpells_OnCommand);
  		}
  
          #region Enhanced Client
          private static void Targeted_Spell(TargetedSpellEventArgs e)
          {
              try
              {
                  Mobile from = e.Mobile;
  
                  if (!DesignContext.Check(from))
                  {
                      return; // They are customizing
                  }
  
                  Spellbook book = null;
                  int spellID = e.SpellID;
  
                  if (book == null || !book.HasSpell(spellID))
                  {
                      book = Find(from, spellID);
                  }
  
                  if (book != null && book.HasSpell(spellID))
                  {
                      SpecialMove move = SpellRegistry.GetSpecialMove(spellID);
  
                      if (move != null)
                      {
                          SpecialMove.SetCurrentMove(from, move);
                      }
                      else
                      {
                          Mobile to = World.FindMobile(e.Target.Serial);
                          Item toI = World.FindItem(e.Target.Serial);
                          Spell spell = SpellRegistry.NewSpell(spellID, from, null);
  
                          if (to != null)
                          {
                              spell.InstantTarget = to;
                          }
                          else if (toI != null)
                          {
                              spell.InstantTarget = toI as IDamageableItem;
                          }
  
                          if (spell != null)
                          {
                              spell.Cast();
                          }
                          else if (!Server.Spells.SkillMasteries.MasteryInfo.IsPassiveMastery(spellID))
                          {
                              from.SendLocalizedMessage(502345); // This spell has been temporarily disabled.
                          }
                      }
                  }
                  else
                  {
                      from.SendLocalizedMessage(500015); // You do not have that spell!
                  }
              }
              catch { }
          }
          #endregion
  
          public static SpellbookType GetTypeForSpell(int spellID)
  		{
  			if (spellID >= 0 && spellID < 64)
  			{
  				return SpellbookType.Regular;
  			}
  			else if (spellID >= 100 && spellID < 117)
  			{
  				return SpellbookType.Necromancer;
  			}
  			else if (spellID >= 200 && spellID < 210)
  			{
  				return SpellbookType.Paladin;
  			}
  			else if (spellID >= 400 && spellID < 406)
  			{
  				return SpellbookType.Samurai;
  			}
  			else if (spellID >= 500 && spellID < 508)
  			{
  				return SpellbookType.Ninja;
  			}
  			else if (spellID >= 600 && spellID < 617)
  			{
  				return SpellbookType.Arcanist;
  			}
  			else if (spellID >= 677 && spellID < 693)
  			{
  				return SpellbookType.Mystic;
  			}
              else if (spellID >= 700 && spellID < 746)
              {
                  return SpellbookType.SkillMasteries;
              }
  
  			return SpellbookType.Invalid;
  		}
  
  		public static Spellbook FindRegular(Mobile from)
  		{
  			return Find(from, -1, SpellbookType.Regular);
  		}
  
  		public static Spellbook FindNecromancer(Mobile from)
  		{
  			return Find(from, -1, SpellbookType.Necromancer);
  		}
  
  		public static Spellbook FindPaladin(Mobile from)
  		{
  			return Find(from, -1, SpellbookType.Paladin);
  		}
  
  		public static Spellbook FindSamurai(Mobile from)
  		{
  			return Find(from, -1, SpellbookType.Samurai);
  		}
  
  		public static Spellbook FindNinja(Mobile from)
  		{
  			return Find(from, -1, SpellbookType.Ninja);
  		}
  
  		public static Spellbook FindArcanist(Mobile from)
  		{
  			return Find(from, -1, SpellbookType.Arcanist);
  		}
  
  		public static Spellbook FindMystic(Mobile from)
  		{
  			return Find(from, -1, SpellbookType.Mystic);
  		}
  
  		public static Spellbook Find(Mobile from, int spellID)
  		{
  			return Find(from, spellID, GetTypeForSpell(spellID));
  		}
  
  		public static Spellbook Find(Mobile from, int spellID, SpellbookType type)
  		{
  			if (from == null)
  			{
  				return null;
  			}
  
  			if (from.Deleted)
  			{
  				m_Table.Remove(from);
  				return null;
  			}
  
  			List<Spellbook> list = null;
  
  			m_Table.TryGetValue(from, out list);
  
  			bool searchAgain = false;
  
  			if (list == null)
  			{
  				m_Table[from] = list = FindAllSpellbooks(from);
  			}
  			else
  			{
  				searchAgain = true;
  			}
  
  			Spellbook book = FindSpellbookInList(list, from, spellID, type);
  
  			if (book == null && searchAgain)
  			{
  				m_Table[from] = list = FindAllSpellbooks(from);
  
  				book = FindSpellbookInList(list, from, spellID, type);
  			}
  
  			return book;
  		}
  
  		public static Spellbook FindSpellbookInList(List<Spellbook> list, Mobile from, int spellID, SpellbookType type)
  		{
  			Container pack = from.Backpack;
  
  			for (int i = list.Count - 1; i >= 0; --i)
  			{
  				if (i >= list.Count)
  				{
  					continue;
  				}
  
  				Spellbook book = list[i];
  
  				if (!book.Deleted && (book.Parent == from || (pack != null && book.Parent == pack)) &&
  					ValidateSpellbook(book, spellID, type))
  				{
  					return book;
  				}
  
  				list.RemoveAt(i);
  			}
  
  			return null;
  		}
  
  		public static List<Spellbook> FindAllSpellbooks(Mobile from)
  		{
  			var list = new List<Spellbook>();
  
  			Item item = from.FindItemOnLayer(Layer.OneHanded);
  
  			if (item is Spellbook)
  			{
  				list.Add((Spellbook)item);
  			}
  
  			Container pack = from.Backpack;
  
  			if (pack == null)
  			{
  				return list;
  			}
  
  			for (int i = 0; i < pack.Items.Count; ++i)
  			{
  				item = pack.Items[i];
  
  				if (item is Spellbook)
  				{
  					list.Add((Spellbook)item);
  				}
  			}
  
  			return list;
  		}
  
  		public static Spellbook FindEquippedSpellbook(Mobile from)
  		{
  			return (from.FindItemOnLayer(Layer.OneHanded) as Spellbook);
  		}
  
  		public static bool ValidateSpellbook(Spellbook book, int spellID, SpellbookType type)
  		{
  			return (book.SpellbookType == type && (spellID == -1 || book.HasSpell(spellID)));
  		}
  
  		public override bool CanEquip(Mobile from)
  		{
  			if (!from.CanBeginAction(typeof(BaseWeapon)))
  			{
  				return false;
  			}
              else if (_Owner != null && _Owner != from)
              {
                  from.SendLocalizedMessage(501023); // You must be the owner to use this item.
                  return false;
              }
              else if (IsVvVItem && !Engines.VvV.ViceVsVirtueSystem.IsVvV(from))
              {
                  from.SendLocalizedMessage(1155496); // This item can only be used by VvV participants!
                  return false;
              }
  
  			return base.CanEquip(from);
  		}
  
  		public override bool AllowEquipedCast(Mobile from)
  		{
  			return true;
  		}
  
  		public override bool OnDragDrop(Mobile from, Item dropped)
  		{
              if (dropped is SpellScroll && !(dropped is SpellStone))
              {
  				SpellScroll scroll = (SpellScroll)dropped;
  
  				SpellbookType type = GetTypeForSpell(scroll.SpellID);
  
  				if (type != SpellbookType)
  				{
  					return false;
  				}
  				else if (HasSpell(scroll.SpellID))
  				{
  					from.SendLocalizedMessage(500179); // That spell is already present in that spellbook.
  					return false;
  				}
  				else
  				{
  					int val = scroll.SpellID - BookOffset;
  
  					if (val >= 0 && val < BookCount)
  					{
  						from.Send(new PlaySound(0x249, GetWorldLocation()));
  
  						m_Content |= (ulong)1 << val;
  						++m_Count;
  
                          if (dropped.Amount > 1)
                          {
                              dropped.Amount--;
                              return base.OnDragDrop(from, dropped);
                          }
                          else
                          {
                              InvalidateProperties();
                              scroll.Delete();
                              return true;
                          }
                      }
  					return false;
  				}
  			}
    		    return false;
  		}
  
  		public override void OnAfterDuped(Item newItem)
  		{
  			Spellbook book = newItem as Spellbook;
  
  			if (book == null)
  			{
  				return;
  			}
  
  			book.m_AosAttributes = new AosAttributes(newItem, m_AosAttributes);
  			book.m_AosSkillBonuses = new AosSkillBonuses(newItem, m_AosSkillBonuses);
              book.m_NegativeAttributes = new NegativeAttributes(newItem, m_NegativeAttributes);
  
              base.OnAfterDuped(newItem);
  		}
  
  		public override void OnAdded(object parent)
  		{
  			if (Core.AOS && parent is Mobile)
  			{
  				Mobile from = (Mobile)parent;
  
  				m_AosSkillBonuses.AddTo(from);
  
  				int strBonus = m_AosAttributes.BonusStr;
  				int dexBonus = m_AosAttributes.BonusDex;
  				int intBonus = m_AosAttributes.BonusInt;
  
  				if (strBonus != 0 || dexBonus != 0 || intBonus != 0)
  				{
  					string modName = Serial.ToString();
  
  					if (strBonus != 0)
  					{
  						from.AddStatMod(new StatMod(StatType.Str, modName + "Str", strBonus, TimeSpan.Zero));
  					}
  
  					if (dexBonus != 0)
  					{
  						from.AddStatMod(new StatMod(StatType.Dex, modName + "Dex", dexBonus, TimeSpan.Zero));
  					}
  
  					if (intBonus != 0)
  					{
  						from.AddStatMod(new StatMod(StatType.Int, modName + "Int", intBonus, TimeSpan.Zero));
  					}
  				}
  
                  if (HasSocket<Caddellite>())
                  {
                      Caddellite.UpdateBuff(from);
                  }
  
  				from.CheckStatTimers();
  			}
  		}
  
  		public override void OnRemoved(object parent)
  		{
  			if (Core.AOS && parent is Mobile)
  			{
  				Mobile from = (Mobile)parent;
  
  				m_AosSkillBonuses.Remove();
  
                  if (HasSocket<Caddellite>())
                  {
                      Caddellite.UpdateBuff(from);
                  }
  
  				string modName = Serial.ToString();
  
  				from.RemoveStatMod(modName + "Str");
  				from.RemoveStatMod(modName + "Dex");
  				from.RemoveStatMod(modName + "Int");
  
  				from.CheckStatTimers();
  			}
  		}
  
  		public bool HasSpell(int spellID)
  		{
  			spellID -= BookOffset;
  
  			return (spellID >= 0 && spellID < BookCount && (m_Content & ((ulong)1 << spellID)) != 0);
  		}
  
  		public void DisplayTo(Mobile to)
  		{
  			// The client must know about the spellbook or it will crash!
  			NetState ns = to.NetState;
  
  			if (ns == null)
  			{
  				return;
  			}
  
  			if (Parent == null)
  			{
  				to.Send(WorldPacket);
  			}
  			else if (Parent is Item)
  			{
  				// What will happen if the client doesn't know about our parent?
  				if (ns.ContainerGridLines)
  				{
  					to.Send(new ContainerContentUpdate6017(this));
  				}
  				else
  				{
  					to.Send(new ContainerContentUpdate(this));
  				}
  			}
  			else if (Parent is Mobile)
  			{
  				// What will happen if the client doesn't know about our parent?
  				to.Send(new EquipUpdate(this));
  			}
  
  			if (ns.HighSeas)
  			{
  				to.Send(new DisplaySpellbookHS(this));
  			}
  			else
  			{
  				to.Send(new DisplaySpellbook(this));
  			}
  
  			if (ObjectPropertyList.Enabled)
  			{
  				if (ns.NewSpellbook)
  				{
  					to.Send(new NewSpellbookContent(this, ItemID, BookOffset + 1, m_Content));
  				}
  				else
  				{
  					if (ns.ContainerGridLines)
  					{
  						to.Send(new SpellbookContent6017(m_Count, BookOffset + 1, m_Content, this));
  					}
  					else
  					{
  						to.Send(new SpellbookContent(m_Count, BookOffset + 1, m_Content, this));
  					}
  				}
  			}
  			else
  			{
  				if (ns.ContainerGridLines)
  				{
  					to.Send(new SpellbookContent6017(m_Count, BookOffset + 1, m_Content, this));
  				}
  				else
  				{
  					to.Send(new SpellbookContent(m_Count, BookOffset + 1, m_Content, this));
  				}
  			}
  		}
  
          public override void AddNameProperties(ObjectPropertyList list)
          {
              base.AddNameProperties(list);
  
  			if (m_Quality == BookQuality.Exceptional)
  			{
  				list.Add(1063341); // exceptional
  			}
  
  			if (m_EngravedText != null)
  			{
                  list.Add(1072305, Utility.FixHtml(m_EngravedText)); // Engraved: ~1_INSCRIPTION~
  			}
  
  			if (m_Crafter != null)
  			{
  				list.Add(1050043, m_Crafter.TitleName); // crafted by ~1_NAME~
  			}
  
              if (IsVvVItem)
              {
                  list.Add(1154937); // VvV Item
              }
  
              if (OwnerName != null)
              {
                  list.Add(1153213, OwnerName);
              }
  
              if (m_NegativeAttributes != null)
              {
                  m_NegativeAttributes.GetProperties(list, this);
              }
  
  			m_AosSkillBonuses.GetProperties(list);
  
  			if (m_Slayer != SlayerName.None)
  			{
  				SlayerEntry entry = SlayerGroup.GetEntryByName(m_Slayer);
  				if (entry != null)
  				{
  					list.Add(entry.Title);
  				}
  			}
  
  			if (m_Slayer2 != SlayerName.None)
  			{
  				SlayerEntry entry = SlayerGroup.GetEntryByName(m_Slayer2);
  				if (entry != null)
  				{
  					list.Add(entry.Title);
  				}
  			}
  
              if (HasSocket<Caddellite>())
              {
                  list.Add(1158662); // Caddellite Infused
              }
  
  			int prop;
  
  			if ((prop = m_AosAttributes.SpellChanneling) != 0)
  			{
  				list.Add(1060482); // spell channeling
  			}
  			
  			if ((prop = m_AosAttributes.NightSight) != 0)
  			{
  				list.Add(1060441); // night sight
  			}
  
  			if ((prop = m_AosAttributes.BonusStr) != 0)
  			{
  				list.Add(1060485, prop.ToString()); // strength bonus ~1_val~
  			}
  			
  			if ((prop = m_AosAttributes.BonusDex) != 0)
  			{
  				list.Add(1060409, prop.ToString()); // dexterity bonus ~1_val~
  			}
  			
  			if ((prop = m_AosAttributes.BonusInt) != 0)
  			{
  				list.Add(1060432, prop.ToString()); // intelligence bonus ~1_val~
  			}
  			
  			if ((prop = m_AosAttributes.BonusHits) != 0)
  			{
  				list.Add(1060431, prop.ToString()); // hit point increase ~1_val~
  			}
  			
  			if ((prop = m_AosAttributes.BonusStam) != 0)
  			{
  				list.Add(1060484, prop.ToString()); // stamina increase ~1_val~
  			}
  			
  			if ((prop = m_AosAttributes.BonusMana) != 0)
  			{
  
gt: 				list.Add(1060439, prop.ToString()); // mana increase ~1_val~