#region References using System; using Server.Mobiles; using Server.Network; using Server.Spells; #endregion namespace Server.Items { public abstract class BaseRanged : BaseMeleeWeapon { public abstract int EffectID { get; } public abstract Type AmmoType { get; } public abstract Item Ammo { get; } public override int DefHitSound { get { return 0x234; } } public override int DefMissSound { get { return 0x238; } } public override SkillName DefSkill { get { return SkillName.Archery; } } public override WeaponType DefType { get { return WeaponType.Ranged; } } public override WeaponAnimation DefAnimation { get { return WeaponAnimation.ShootXBow; } } public override SkillName AccuracySkill { get { return SkillName.Archery; } } private Timer m_RecoveryTimer; // so we don't start too many timers private int m_Velocity; [CommandProperty(AccessLevel.GameMaster)] public bool Balanced { get { return Attributes.BalancedWeapon > 0; } set { if (value) Attributes.BalancedWeapon = 1; else Attributes.BalancedWeapon = 0; } } [CommandProperty(AccessLevel.GameMaster)] public int Velocity { get { return m_Velocity; } set { m_Velocity = value; InvalidateProperties(); } } public BaseRanged(int itemID) : base(itemID) { } public BaseRanged(Serial serial) : base(serial) { } public override TimeSpan OnSwing(Mobile attacker, IDamageable damageable) { long nextShoot; if (attacker is PlayerMobile) nextShoot = ((PlayerMobile)attacker).NextMovementTime + (Core.SE ? 250 : Core.AOS ? 500 : 1000); else nextShoot = attacker.LastMoveTime + attacker.ComputeMovementSpeed(); // Make sure we've been standing still for .25/.5/1 second depending on Era if (nextShoot <= Core.TickCount || (Core.AOS && WeaponAbility.GetCurrentAbility(attacker) is MovingShot)) { bool canSwing = true; if (Core.AOS) { canSwing = (!attacker.Paralyzed && !attacker.Frozen); if (canSwing) { Spell sp = attacker.Spell as Spell; canSwing = (sp == null || !sp.IsCasting || !sp.BlocksMovement); } } if (canSwing && attacker.HarmfulCheck(damageable)) { attacker.DisruptiveAction(); attacker.Send(new Swing(0, attacker, damageable)); if (OnFired(attacker, damageable)) { if (CheckHit(attacker, damageable)) { OnHit(attacker, damageable); } else { OnMiss(attacker, damageable); } } } attacker.RevealingAction(); return GetDelay(attacker); } attacker.RevealingAction(); return TimeSpan.FromSeconds(0.25); } public override void OnHit(Mobile attacker, IDamageable damageable, double damageBonus) { if (AmmoType != null && attacker.Player && damageable is Mobile && !((Mobile)damageable).Player && (((Mobile)damageable).Body.IsAnimal || ((Mobile)damageable).Body.IsMonster) && 0.4 >= Utility.RandomDouble()) { var ammo = Ammo; if (ammo != null) { ((Mobile)damageable).AddToBackpack(ammo); } } base.OnHit(attacker, damageable, damageBonus); } public override void OnMiss(Mobile attacker, IDamageable damageable) { if (attacker.Player && 0.4 >= Utility.RandomDouble()) { if (Core.SE) { PlayerMobile p = attacker as PlayerMobile; if (p != null && AmmoType != null) { Type ammo = AmmoType; if (p.RecoverableAmmo.ContainsKey(ammo)) { p.RecoverableAmmo[ammo]++; } else { p.RecoverableAmmo.Add(ammo, 1); } if (!p.Warmode) { if (m_RecoveryTimer == null) { m_RecoveryTimer = Timer.DelayCall(TimeSpan.FromSeconds(10), p.RecoverAmmo); } if (!m_RecoveryTimer.Running) { m_RecoveryTimer.Start(); } } } } else { Point3D loc = damageable.Location; var ammo = Ammo; if (ammo != null) { ammo.MoveToWorld( new Point3D(loc.X + Utility.RandomMinMax(-1, 1), loc.Y + Utility.RandomMinMax(-1, 1), loc.Z), damageable.Map); } } } base.OnMiss(attacker, damageable); } public virtual bool OnFired(Mobile attacker, IDamageable damageable) { WeaponAbility ability = WeaponAbility.GetCurrentAbility(attacker); // Respect special moves that use no ammo if (ability != null && ability.ConsumeAmmo == false) { return true; } if (attacker.Player) { BaseQuiver quiver = attacker.FindItemOnLayer(Layer.Cloak) as BaseQuiver; Container pack = attacker.Backpack; int lowerAmmo = AosAttributes.GetValue(attacker, AosAttribute.LowerAmmoCost); if (quiver == null || Utility.Random(100) >= lowerAmmo) { // consume ammo if (quiver != null && quiver.ConsumeTotal(AmmoType, 1)) { quiver.InvalidateWeight(); } else if (pack == null || !pack.ConsumeTotal(AmmoType, 1)) { return false; } } else if (quiver.FindItemByType(AmmoType) == null && (pack == null || pack.FindItemByType(AmmoType) == null)) { // lower ammo cost should not work when we have no ammo at all return false; } } attacker.MovingEffect(damageable, EffectID, 18, 1, false, false); return true; } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write(4); // version writer.Write(m_Velocity); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); switch (version) { case 4: case 3: { if (version == 3 && reader.ReadBool()) Attributes.BalancedWeapon = 1; m_Velocity = reader.ReadInt(); goto case 2; } case 2: case 1: { break; } case 0: { /*m_EffectID =*/ reader.ReadInt(); break; } } if (version < 2) { WeaponAttributes.MageWeapon = 0; WeaponAttributes.UseBestSkill = 0; } } } }