context: using System;
  using System.Collections;
  using Server.Engines.Craft;
  using Server.Mobiles;
  using Server.Network;
  using Server.Targeting;
  
  namespace Server.Items
  {
      public delegate void InstrumentPickedCallback(Mobile from, BaseInstrument instrument);
  
      public enum InstrumentQuality
      {
          Low,
          Regular,
          Exceptional
      }
  
      public abstract class BaseInstrument : Item, ICraftable, ISlayer
      {
          private int m_WellSound, m_BadlySound;
          private SlayerName m_Slayer, m_Slayer2;
          private InstrumentQuality m_Quality;
          private Mobile m_Crafter;
          private int m_UsesRemaining;
  
          [CommandProperty(AccessLevel.GameMaster)]
          public int SuccessSound
          {
              get
              {
                  return this.m_WellSound;
              }
              set
              {
                  this.m_WellSound = value;
              }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public int FailureSound
          {
              get
              {
                  return this.m_BadlySound;
              }
              set
              {
                  this.m_BadlySound = value;
              }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public SlayerName Slayer
          {
              get
              {
                  return this.m_Slayer;
              }
              set
              {
                  this.m_Slayer = value;
                  this.InvalidateProperties();
              }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public SlayerName Slayer2
          {
              get
              {
                  return this.m_Slayer2;
              }
              set
              {
                  this.m_Slayer2 = value;
                  this.InvalidateProperties();
              }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public InstrumentQuality Quality
          {
              get
              {
                  return this.m_Quality;
              }
              set
              {
                  this.UnscaleUses();
                  this.m_Quality = value;
                  this.InvalidateProperties();
                  this.ScaleUses();
              }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public Mobile Crafter
          {
              get
              {
                  return this.m_Crafter;
              }
              set
              {
                  this.m_Crafter = value;
                  this.InvalidateProperties();
              }
          }
  
          public virtual int InitMinUses
          {
              get
              {
                  return 350;
              }
          }
          public virtual int InitMaxUses
          {
              get
              {
                  return 450;
              }
          }
  
          public virtual TimeSpan ChargeReplenishRate
          {
              get
              {
                  return TimeSpan.FromMinutes(5.0);
              }
          }
  
          [CommandProperty(AccessLevel.GameMaster)]
          public int UsesRemaining
          {
              get
              {
                  this.CheckReplenishUses();
                  return this.m_UsesRemaining;
              }
              set
              {
                  this.m_UsesRemaining = value;
                  this.InvalidateProperties();
              }
          }
  
          private DateTime m_LastReplenished;
  
          [CommandProperty(AccessLevel.GameMaster)]
          public DateTime LastReplenished
          {
              get
              {
                  return this.m_LastReplenished;
              }
              set
              {
                  this.m_LastReplenished = value;
                  this.CheckReplenishUses();
              }
          }
  
          private bool m_ReplenishesCharges;
          [CommandProperty(AccessLevel.GameMaster)]
          public bool ReplenishesCharges
          {
              get
              {
                  return this.m_ReplenishesCharges;
              }
              set 
              {
                  if (value != this.m_ReplenishesCharges && value)
                      this.m_LastReplenished = DateTime.UtcNow;
  
                  this.m_ReplenishesCharges = value; 
              }
          }
  
          public void RandomInstrument()
          {
              switch (Utility.Random(3))
              {
                  case 0:
                      {
                          this.ItemID = 0xEB2;
                          this.SuccessSound = 0x45;
                          this.FailureSound = 0x46;
                          break;
                      }
                  case 1:
                      {
                          this.ItemID = 0xEB3;
                          this.SuccessSound = 0x4C;
                          this.FailureSound = 0x4D;
                          break;
                      }
                  default:
                      {
                          this.ItemID = 0xE9C;
                          this.SuccessSound = 0x38;
                          this.FailureSound = 0x39;
                          break;
                      }
              }
          }
  
          public void CheckReplenishUses()
          {
              this.CheckReplenishUses(true);
          }
  
          public void CheckReplenishUses(bool invalidate)
          {
              if (!this.m_ReplenishesCharges || this.m_UsesRemaining >= this.InitMaxUses)
                  return;
  
              if (this.m_LastReplenished + this.ChargeReplenishRate < DateTime.UtcNow)
              {
                  TimeSpan timeDifference = DateTime.UtcNow - this.m_LastReplenished;
  
                  this.m_UsesRemaining = Math.Min(this.m_UsesRemaining + (int)(timeDifference.Ticks / this.ChargeReplenishRate.Ticks), this.InitMaxUses);	//How rude of TimeSpan to not allow timespan division.
                  this.m_LastReplenished = DateTime.UtcNow;
  
                  if (invalidate)
                      this.InvalidateProperties();
              }
          }
  
          public void ScaleUses()
          {
              this.UsesRemaining = (this.UsesRemaining * this.GetUsesScalar()) / 100;
              //InvalidateProperties();
          }
  
          public void UnscaleUses()
          {
              this.UsesRemaining = (this.UsesRemaining * 100) / this.GetUsesScalar();
          }
  
          public int GetUsesScalar()
          {
              if (this.m_Quality == InstrumentQuality.Exceptional)
                  return 200;
  
              return 100;
          }
  
          public void ConsumeUse(Mobile from)
          {
              // TODO: Confirm what must happen here?
              if (this.UsesRemaining > 1)
              {
                  --this.UsesRemaining;
              }
              else
              {
                  if (from != null)
                      from.SendLocalizedMessage(502079); // The instrument played its last tune.
  
                  this.Delete();
              }
          }
  
          private static readonly Hashtable m_Instruments = new Hashtable();
  
          public static BaseInstrument GetInstrument(Mobile from)
          {
              BaseInstrument item = m_Instruments[from] as BaseInstrument;
  
              if (item == null)
                  return null;
  
              if (!item.IsChildOf(from.Backpack))
              {
                  m_Instruments.Remove(from);
                  return null;
              }
  
              return item;
          }
  
          public static int GetBardRange(Mobile bard, SkillName skill)
          {
              return 8 + (int)(bard.Skills[skill].Value / 15);
          }
  
          public static void PickInstrument(Mobile from, InstrumentPickedCallback callback)
          {
              BaseInstrument instrument = GetInstrument(from);
  
              if (instrument != null)
              {
                  if (callback != null)
                      callback(from, instrument);
              }
              else
              {
                  from.SendLocalizedMessage(500617); // What instrument shall you play?
                  from.BeginTarget(1, false, TargetFlags.None, new TargetStateCallback(OnPickedInstrument), callback);
              }
          }
  
          public static void OnPickedInstrument(Mobile from, object targeted, object state)
          {
              BaseInstrument instrument = targeted as BaseInstrument;
  
              if (instrument == null)
              {
                  from.SendLocalizedMessage(500619); // That is not a musical instrument.
              }
              else
              {
                  SetInstrument(from, instrument);
  
                  InstrumentPickedCallback callback = state as InstrumentPickedCallback;
  
                  if (callback != null)
                      callback(from, instrument);
              }
          }
  
          public static bool IsMageryCreature(BaseCreature bc)
          {
              return (bc != null && bc.AI == AIType.AI_Mage && bc.Skills[SkillName.Magery].Base > 5.0);
          }
  
          public static bool IsFireBreathingCreature(BaseCreature bc)
          {
              if (bc == null)
                  return false;
  
              return bc.HasBreath;
          }
  
          public static bool IsPoisonImmune(BaseCreature bc)
          {
              return (bc != null && bc.PoisonImmune != null);
          }
  
          public static int GetPoisonLevel(BaseCreature bc)
          {
              if (bc == null)
                  return 0;
  
              Poison p = bc.HitPoison;
  
              if (p == null)
                  return 0;
  
              return p.Level + 1;
          }
  
          public static double GetBaseDifficulty(Mobile targ)
          {
              /* Difficulty TODO: Add another 100 points for each of the following abilities:
              - Radiation or Aura Damage (Heat, Cold etc.)
              - Summoning Undead
              */
              double val = (targ.HitsMax * 1.6) + targ.StamMax + targ.ManaMax;
  
              val += targ.SkillsTotal / 10;
  
              BaseCreature bc = targ as BaseCreature;
  
              if (IsMageryCreature(bc))
                  val += 100;
  
              if (IsFireBreathingCreature(bc))
                  val += 100;
  
              if (IsPoisonImmune(bc))
                  val += 100;
  
              if (targ is VampireBat || targ is VampireBatFamiliar)
                  val += 100;
  
  
gt:             val += GetPoisonLevel(bc) * 20;