using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using ModernUO.Serialization;
using Server.Collections;
using Server.Engines.Craft;
using Server.Engines.Virtues;
using Server.Ethics;
using Server.Factions;
using Server.Mobiles;
using Server.Network;
using Server.SkillHandlers;
using Server.Spells;
using Server.Spells.Bushido;
using Server.Spells.Chivalry;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
using Server.Spells.Sixth;
using Server.Spells.Spellweaving;
using Server.Text;

namespace Server.Items;

public interface ISlayer
{
    SlayerName Slayer { get; set; }
    SlayerName Slayer2 { get; set; }
}

[SerializationGenerator(10, false)]
public abstract partial class BaseWeapon
    : Item, IWeapon, IFactionItem, ICraftable, ISlayer, IDurability, IAosItem, IIdentifiable
{
    private static bool _enableInstaHit;

    public static void Configure()
    {
        _enableInstaHit = ServerConfiguration.GetSetting("melee.enableInstaHit", !Core.UOR);
    }

    /* Weapon internals work differently now (Mar 13 2003)
     *
     * The attributes defined below default to -1.
     * If the value is -1, the corresponding virtual 'Aos/Old' property is used.
     * If not, the attribute value itself is used. Here's the list:
     *  - MinDamage
     *  - MaxDamage
     *  - Speed
     *  - HitSound
     *  - MissSound
     *  - StrRequirement, DexRequirement, IntRequirement
     *  - WeaponType
     *  - WeaponAnimation
     *  - MaxRange
     */

    [InvalidateProperties]
    [SerializableField(0)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private WeaponDamageLevel _damageLevel;

    [SerializableFieldSaveFlag(0)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeDamageLevel() => _damageLevel != WeaponDamageLevel.Regular;

    [InvalidateProperties]
    [SerializableField(5)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private int _maxHitPoints;

    [SerializableFieldSaveFlag(5)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeMaxHitPoints() => _maxHitPoints != 0;

    [InvalidateProperties]
    [SerializableField(6)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private SlayerName _slayer;

    [SerializableFieldSaveFlag(6)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeSlayer() => _slayer != SlayerName.None;

    [InvalidateProperties]
    [SerializableField(7)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private Poison _poison;

    [SerializableFieldSaveFlag(7)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializePoison() => _poison != null;

    [InvalidateProperties]
    [SerializableField(8)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private int _poisonCharges;

    [SerializableFieldSaveFlag(8)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializePoisonCharges() => _poisonCharges > 0;

    [InvalidateProperties]
    [SerializableField(9)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private string _crafter;

    [SerializableFieldSaveFlag(9)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeCrafter() => !string.IsNullOrEmpty(_crafter);

    [InvalidateProperties]
    [SerializableField(10)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private bool _identified;

    [SerializableFieldSaveFlag(10)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeIdentified() => _identified;

    [SerializedIgnoreDupe]
    [SerializableField(24, setter: "private")]
    [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)]
    private AosAttributes _attributes;

    [SerializableFieldSaveFlag(24)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeAttributes() => !_attributes.IsEmpty;

    [SerializableFieldDefault(24)]
    private AosAttributes AttributesDefaultValue() => new(this);

    [SerializedIgnoreDupe]
    [SerializableField(25, setter: "private")]
    [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)]
    private AosWeaponAttributes _weaponAttributes;

    [SerializableFieldSaveFlag(25)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeWeaponAttributes() => !_weaponAttributes.IsEmpty;

    [SerializableFieldDefault(25)]
    private AosWeaponAttributes WeaponAttributesDefaultValue() => new(this);

    [SerializableField(26)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private bool _playerConstructed;

    [SerializableFieldSaveFlag(26)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializePlayerConstructed() => _playerConstructed;

    [SerializedIgnoreDupe]
    [SerializableField(27, setter: "private")]
    [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)]
    private AosSkillBonuses _skillBonuses;

    [SerializableFieldSaveFlag(27)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeSkillBonuses() => !_skillBonuses.IsEmpty;

    [SerializableFieldDefault(27)]
    private AosSkillBonuses SkillBonusesDefaultValue() => new(this);

    [InvalidateProperties]
    [SerializableField(28)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private SlayerName _slayer2;

    [SerializableFieldSaveFlag(28)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeSlayer2() => _slayer2 != SlayerName.None;

    [SerializedIgnoreDupe]
    [SerializableField(29, setter: "private")]
    [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)]
    private AosElementAttributes _aosElementDamages;

    [SerializableFieldSaveFlag(29)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeElementAttributes() => !_aosElementDamages.IsEmpty;

    [SerializableFieldDefault(29)]
    private AosElementAttributes AosElementAttributesDefaultValue() => new(this);

    [InvalidateProperties]
    [SerializableField(30)]
    [SerializedCommandProperty(AccessLevel.GameMaster)]
    private string _engravedText;

    [SerializableFieldSaveFlag(30)]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldSerializeEngravedText() => !string.IsNullOrEmpty(_engravedText);

    private FactionItem m_FactionState;
    private SkillMod m_SkillMod, m_MageMod;

    public BaseWeapon(int itemID) : base(itemID)
    {
        Layer = (Layer)ItemData.Quality;

        _quality = WeaponQuality.Regular;
        _strRequirement = -1;
        _dexRequirement = -1;
        _intRequirement = -1;
        _minDamage = -1;
        _maxDamage = -1;
        _hitSound = -1;
        _missSound = -1;
        _speed = -1;
        _maxRange = -1;
        _skill = (SkillName)(-1);
        _type = (WeaponType)(-1);
        _animation = (WeaponAnimation)(-1);

        _hitPoints = _maxHitPoints = Utility.RandomMinMax(InitMinHits, InitMaxHits);

        _resource = CraftResource.Iron;

        Attributes = new AosAttributes(this);
        WeaponAttributes = new AosWeaponAttributes(this);
        SkillBonuses = new AosSkillBonuses(this);
        AosElementDamages = new AosElementAttributes(this);
    }

    public virtual bool UseSkillMod => !Core.AOS;

    public static bool InDoubleStrike { get; set; }

    public virtual int VirtualDamageBonus => 0;

    [Hue]
    [CommandProperty(AccessLevel.GameMaster)]
    public override int Hue
    {
        get => base.Hue;
        set
        {
            base.Hue = value;
            InvalidateProperties();
        }
    }

    public virtual int ArtifactRarity => 0;

    public virtual WeaponAbility PrimaryAbility => null;
    public virtual WeaponAbility SecondaryAbility => null;

    public virtual int DefMaxRange => 1;
    public virtual int DefHitSound => 0;
    public virtual int DefMissSound => 0;
    public virtual SkillName DefSkill => SkillName.Swords;
    public virtual WeaponType DefType => WeaponType.Slashing;
    public virtual WeaponAnimation DefAnimation => WeaponAnimation.Slash1H;

    public virtual int AosStrengthReq => 0;
    public virtual int AosDexterityReq => 0;
    public virtual int AosIntelligenceReq => 0;
    public virtual int AosMinDamage => 0;
    public virtual int AosMaxDamage => 0;
    public virtual int AosSpeed => 0;
    public virtual float MlSpeed => 0.0f;
    public virtual int AosMaxRange => DefMaxRange;
    public virtual int AosHitSound => DefHitSound;
    public virtual int AosMissSound => DefMissSound;
    public virtual SkillName AosSkill => DefSkill;
    public virtual WeaponType AosType => DefType;
    public virtual WeaponAnimation AosAnimation => DefAnimation;

    public virtual int OldStrengthReq => 0;
    public virtual int OldDexterityReq => 0;
    public virtual int OldIntelligenceReq => 0;
    public virtual int OldMinDamage => 0;
    public virtual int OldMaxDamage => 0;
    public virtual int OldSpeed => 0;
    public virtual int OldMaxRange => DefMaxRange;
    public virtual int OldHitSound => DefHitSound;
    public virtual int OldMissSound => DefMissSound;
    public virtual SkillName OldSkill => DefSkill;
    public virtual WeaponType OldType => DefType;
    public virtual WeaponAnimation OldAnimation => DefAnimation;

    public override int PhysicalResistance => WeaponAttributes.ResistPhysicalBonus;
    public override int FireResistance => WeaponAttributes.ResistFireBonus;
    public override int ColdResistance => WeaponAttributes.ResistColdBonus;
    public override int PoisonResistance => WeaponAttributes.ResistPoisonBonus;
    public override int EnergyResistance => WeaponAttributes.ResistEnergyBonus;

    public virtual SkillName AccuracySkill => SkillName.Tactics;

    [CommandProperty(AccessLevel.GameMaster)]
    public bool Cursed { get; set; }

    [CommandProperty(AccessLevel.GameMaster)]
    public bool Consecrated { get; set; }

    [SerializableProperty(1)]
    [CommandProperty(AccessLevel.GameMaster)]
    public WeaponAccuracyLevel AccuracyLevel
    {
        get => _accuracyLevel;
        set
        {
            if (_accuracyLevel != value)
            {
                _accuracyLevel = value;

                if (UseSkillMod)
                {
                    if (_accuracyLevel == WeaponAccuracyLevel.Regular)
                    {
                        m_SkillMod?.Remove();

                        m_SkillMod = null;
                    }
                    else if (m_SkillMod == null && Parent is Mobile mobile)
                    {
                        m_SkillMod = new DefaultSkillMod(AccuracySkill, "WeaponAccuracy", true, (int)_accuracyLevel * 5);
                        mobile.AddSkillMod(m_SkillMod);
                    }
                    else if (m_SkillMod != null)
                    {
                        m_SkillMod.Value = (int)_accuracyLevel * 5;
                    }
                }

                InvalidateProperties();
                this.MarkDirty();
            }
        }
    }

    [SerializableFieldSaveFlag(1)]
    private bool ShouldSerializeWeaponAccuracy() => _accuracyLevel != WeaponAccuracyLevel.Regular;

    [SerializableProperty(2)]
    [CommandProperty(AccessLevel.GameMaster)]
    public WeaponDurabilityLevel DurabilityLevel
    {
        get => _durabilityLevel;
        set
        {
            UnscaleDurability();
            _durabilityLevel = value;
            InvalidateProperties();
            ScaleDurability();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(2)]
    private bool ShouldSerializeDurabilityLevel() => _durabilityLevel != WeaponDurabilityLevel.Regular;

    [SerializableProperty(3)]
    [CommandProperty(AccessLevel.GameMaster)]
    public WeaponQuality Quality
    {
        get => _quality;
        set
        {
            UnscaleDurability();
            _quality = value;
            ScaleDurability();
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(3)]
    private bool ShouldSerializeQuality() => _quality != WeaponQuality.Regular;

    [SerializableFieldDefault(3)]
    private WeaponQuality QualityDefaultValue() => WeaponQuality.Regular;

    [SerializableProperty(4)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int HitPoints
    {
        get => _hitPoints;
        set
        {
            if (_hitPoints == value)
            {
                return;
            }

            if (value > _maxHitPoints)
            {
                value = _maxHitPoints;
            }

            _hitPoints = value;

            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(4)]
    private bool ShouldSerializeHitPoints() => _hitPoints > 0;

    [SerializableProperty(11)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int StrRequirement
    {
        get => _strRequirement == -1 ? Core.AOS ? AosStrengthReq : OldStrengthReq : _strRequirement;
        set
        {
            _strRequirement = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(11)]
    private bool ShouldSerializeStrReq() => _strRequirement != -1;

    [SerializableFieldDefault(11)]
    private int StrReqDefaultValue() => -1;

    [SerializableProperty(12)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int DexRequirement
    {
        get => _dexRequirement == -1 ? Core.AOS ? AosDexterityReq : OldDexterityReq : _dexRequirement;
        set
        {
            _dexRequirement = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(12)]
    private bool ShouldSerializeDexReq() => _dexRequirement != -1;

    [SerializableFieldDefault(12)]
    private int DexReqDefaultValue() => -1;

    [SerializableProperty(13)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int IntRequirement
    {
        get => _intRequirement == -1 ? Core.AOS ? AosIntelligenceReq : OldIntelligenceReq : _intRequirement;
        set
        {
            _intRequirement = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(13)]
    private bool ShouldSerializeIntReq() => _intRequirement != -1;

    [SerializableFieldDefault(13)]
    private int IntReqDefaultValue() => -1;

    [SerializableProperty(14)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int MinDamage
    {
        get => _minDamage == -1 ? Core.AOS ? AosMinDamage : OldMinDamage : _minDamage;
        set
        {
            _minDamage = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(14)]
    private bool ShouldSerializeMinDamage() => _minDamage != -1;

    [SerializableFieldDefault(14)]
    private int MinDamageDefaultValue() => -1;

    [SerializableProperty(15)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int MaxDamage
    {
        get => _maxDamage == -1 ? Core.AOS ? AosMaxDamage : OldMaxDamage : _maxDamage;
        set
        {
            _maxDamage = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(15)]
    private bool ShouldSerializeMaxDamage() => _maxDamage != -1;

    [SerializableFieldDefault(15)]
    private int MaxDamageDefaultValue() => -1;

    [SerializableProperty(16)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int HitSound
    {
        get => _hitSound == -1 ? Core.AOS ? AosHitSound : OldHitSound : _hitSound;
        set
        {
            _hitSound = value;
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(16)]
    private bool ShouldSerializeHitSound() => _hitSound != -1;

    [SerializableFieldDefault(16)]
    private int HitSoundDefaultValue() => -1;

    [SerializableProperty(17)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int MissSound
    {
        get => _missSound == -1 ? Core.AOS ? AosMissSound : OldMissSound : _missSound;
        set
        {
            _missSound = value;
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(17)]
    private bool ShouldSerializeMissSound() => _missSound != -1;

    [SerializableFieldDefault(17)]
    private int MissSoundDefaultValue() => -1;

    [SerializableProperty(18)]
    [CommandProperty(AccessLevel.GameMaster)]
    public float Speed
    {
        get
        {
            if (_speed != -1)
            {
                return _speed;
            }

            if (Core.ML)
            {
                return MlSpeed;
            }

            if (Core.AOS)
            {
                return AosSpeed;
            }

            return OldSpeed;
        }
        set
        {
            _speed = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(18)]
    private bool ShouldSerializeSpeed() => _speed != -1;

    [SerializableFieldDefault(18)]
    private float SpeedDefaultValue() => -1;

    [SerializableProperty(19)]
    [CommandProperty(AccessLevel.GameMaster)]
    public int MaxRange
    {
        get => _maxRange == -1 ? Core.AOS ? AosMaxRange : OldMaxRange : _maxRange;
        set
        {
            _maxRange = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(19)]
    private bool ShouldSerializeMaxRange() => _maxRange != -1;

    [SerializableFieldDefault(19)]
    private int MaxRangeDefaultValue() => -1;

    [SerializableProperty(20)]
    [CommandProperty(AccessLevel.GameMaster)]
    public SkillName Skill
    {
        get => _skill == (SkillName)(-1) ? Core.AOS ? AosSkill : OldSkill : _skill;
        set
        {
            _skill = value;
            InvalidateProperties();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(20)]
    private bool ShouldSerializeSkill() => _skill != (SkillName)(-1);

    [SerializableFieldDefault(20)]
    private SkillName SkillNameDefaultValue() => (SkillName)(-1);

    [SerializableProperty(21)]
    [CommandProperty(AccessLevel.GameMaster)]
    public WeaponType Type
    {
        get => _type == (WeaponType)(-1) ? Core.AOS ? AosType : OldType : _type;
        set
        {
            _type = value;
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(21)]
    private bool ShouldSerializeType() => _type != (WeaponType)(-1);

    [SerializableFieldDefault(21)]
    private WeaponType TypeDefaultValue() => (WeaponType)(-1);

    [SerializableProperty(22)]
    [CommandProperty(AccessLevel.GameMaster)]
    public WeaponAnimation Animation
    {
        get => _animation == (WeaponAnimation)(-1) ? Core.AOS ? AosAnimation : OldAnimation : _animation;
        set
        {
            _animation = value;
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(22)]
    private bool ShouldSerializeAnimation() => _animation != (WeaponAnimation)(-1);

    [SerializableFieldDefault(22)]
    private WeaponAnimation AnimationDefaultValue() => (WeaponAnimation)(-1);

    [SerializableProperty(23)]
    [CommandProperty(AccessLevel.GameMaster)]
    public CraftResource Resource
    {
        get => _resource;
        set
        {
            UnscaleDurability();
            _resource = value;
            Hue = CraftResources.GetHue(_resource);
            InvalidateProperties();
            ScaleDurability();
            this.MarkDirty();
        }
    }

    [SerializableFieldSaveFlag(23)]
    private bool ShouldSerializeResource() => _resource != CraftResource.Iron;

    [SerializableFieldDefault(23)]
    private CraftResource ResourceDefaultValue() => CraftResource.Iron;

    public virtual int OnCraft(
        int quality, bool makersMark, Mobile from, CraftSystem craftSystem, Type typeRes, BaseTool tool,
        CraftItem craftItem, int resHue
    )
    {
        Quality = (WeaponQuality)quality;

        if (makersMark)
        {
            Crafter = from.RawName;
        }

        PlayerConstructed = true;
        Identified = true;

        var resourceType = typeRes ?? craftItem.Resources[0].ItemType;

        if (Core.AOS)
        {
            Resource = CraftResources.GetFromType(resourceType);

            var context = craftSystem.GetContext(from);

            if (context?.DoNotColor == true)
            {
                Hue = 0;
            }

            if (tool is BaseRunicTool runicTool)
            {
                runicTool.ApplyAttributesTo(this);
            }

            if (Quality == WeaponQuality.Exceptional)
            {
                Attributes.WeaponDamage = 35;

                if (Core.ML)
                {
                    Attributes.WeaponDamage += (int)(from.Skills.ArmsLore.Value / 20);

                    if (Attributes.WeaponDamage > 50)
                    {
                        Attributes.WeaponDamage = 50;
                    }

                    from.CheckSkill(SkillName.ArmsLore, 0, 100);
                }
            }
        }
        else if (tool is BaseRunicTool runicTool)
        {
            var thisResource = CraftResources.GetFromType(resourceType);

            if (thisResource == runicTool.Resource)
            {
                Resource = thisResource;

                var context = craftSystem.GetContext(from);

                if (context?.DoNotColor == true)
                {
                    Hue = 0;
                }

                switch (thisResource)
                {
                    case CraftResource.DullCopper:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Durable;
                            AccuracyLevel = WeaponAccuracyLevel.Accurate;
                            break;
                        }
                    case CraftResource.ShadowIron:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Durable;
                            DamageLevel = WeaponDamageLevel.Ruin;
                            break;
                        }
                    case CraftResource.Copper:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Fortified;
                            DamageLevel = WeaponDamageLevel.Ruin;
                            AccuracyLevel = WeaponAccuracyLevel.Surpassingly;
                            break;
                        }
                    case CraftResource.Bronze:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Fortified;
                            DamageLevel = WeaponDamageLevel.Might;
                            AccuracyLevel = WeaponAccuracyLevel.Surpassingly;
                            break;
                        }
                    case CraftResource.Gold:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Indestructible;
                            DamageLevel = WeaponDamageLevel.Force;
                            AccuracyLevel = WeaponAccuracyLevel.Eminently;
                            break;
                        }
                    case CraftResource.Agapite:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Indestructible;
                            DamageLevel = WeaponDamageLevel.Power;
                            AccuracyLevel = WeaponAccuracyLevel.Eminently;
                            break;
                        }
                    case CraftResource.Verite:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Indestructible;
                            DamageLevel = WeaponDamageLevel.Power;
                            AccuracyLevel = WeaponAccuracyLevel.Exceedingly;
                            break;
                        }
                    case CraftResource.Valorite:
                        {
                            DurabilityLevel = WeaponDurabilityLevel.Indestructible;
                            DamageLevel = WeaponDamageLevel.Vanq;
                            AccuracyLevel = WeaponAccuracyLevel.Supremely;
                            break;
                        }
                }
            }
        }

        return quality;
    }

    public virtual void UnscaleDurability()
    {
        var scale = 100 + GetDurabilityBonus();

        _hitPoints = (_hitPoints * 100 + (scale - 1)) / scale;
        _maxHitPoints = (_maxHitPoints * 100 + (scale - 1)) / scale;
        InvalidateProperties();
    }

    public virtual void ScaleDurability()
    {
        var scale = 100 + GetDurabilityBonus();

        _hitPoints = (_hitPoints * scale + 99) / 100;
        _maxHitPoints = (_maxHitPoints * scale + 99) / 100;
        InvalidateProperties();
    }

    public virtual int InitMinHits => 0;
    public virtual int InitMaxHits => 0;

    public virtual bool CanFortify => true;

    public FactionItem FactionItemState
    {
        get => m_FactionState;
        set
        {
            m_FactionState = value;

            if (m_FactionState == null)
            {
                Hue = CraftResources.GetHue(Resource);
            }

            LootType = m_FactionState == null ? LootType.Regular : LootType.Blessed;
        }
    }

    public virtual void OnBeforeSwing(Mobile attacker, Mobile defender)
    {
        if (WeaponAbility.GetCurrentAbility(attacker)?.OnBeforeSwing(attacker, defender) == false)
        {
            WeaponAbility.ClearCurrentAbility(attacker);
        }

        if (SpecialMove.GetCurrentMove(attacker)?.OnBeforeSwing(attacker, defender) == false)
        {
            SpecialMove.ClearCurrentMove(attacker);
        }
    }

    public virtual void GetStatusDamage(Mobile from, out int min, out int max)
    {
        GetBaseDamageRange(from, out var baseMin, out var baseMax);

        if (Core.AOS)
        {
            min = Math.Max((int)ScaleDamageAOS(from, baseMin, false), 1);
            max = Math.Max((int)ScaleDamageAOS(from, baseMax, false), 1);
        }
        else
        {
            min = Math.Max((int)ScaleDamageOld(from, baseMin, false), 1);
            max = Math.Max((int)ScaleDamageOld(from, baseMax, false), 1);
        }
    }

    public virtual TimeSpan OnSwing(Mobile attacker, Mobile defender, double damageBonus = 1.0)
    {
        var canSwing = true;

        if (Core.AOS)
        {
            canSwing = !attacker.Paralyzed && !attacker.Frozen;

            if (canSwing)
            {
                canSwing = attacker.Spell is not Spell sp || !sp.IsCasting || !sp.BlocksMovement;
            }

            if (canSwing)
            {
                canSwing = attacker is not PlayerMobile p || p.PeacedUntil <= Core.Now;
            }
        }

        if ((attacker as PlayerMobile)?.DuelContext?.CheckItemEquip(attacker, this) == false)
        {
            canSwing = false;
        }

        if (canSwing && attacker.HarmfulCheck(defender))
        {
            attacker.DisruptiveAction();

            attacker.NetState?.SendSwing(attacker.Serial, defender.Serial);

            if (attacker is BaseCreature bc)
            {
                if (bc.TriggerAbility(MonsterAbilityTrigger.CombatAction, defender))
                {
                    return GetDelay(attacker);
                }

                // Only change direction if they are not a player.
                attacker.Direction = attacker.GetDirectionTo(defender);
                var ab = bc.GetWeaponAbility();

                if (ab != null)
                {
                    if (bc.WeaponAbilityChance > Utility.RandomDouble())
                    {
                        WeaponAbility.SetCurrentAbility(bc, ab);
                    }
                    else
                    {
                        WeaponAbility.ClearCurrentAbility(bc);
                    }
                }
            }

            if (CheckHit(attacker, defender))
            {
                OnHit(attacker, defender, damageBonus);
            }
            else
            {
                OnMiss(attacker, defender);
            }
        }

        return GetDelay(attacker);
    }

    public override void OnAfterDuped(Item newItem)
    {
        if (newItem is not BaseWeapon weap)
        {
            return;
        }

        weap.Attributes = new AosAttributes(newItem, Attributes);
        weap.AosElementDamages = new AosElementAttributes(newItem, AosElementDamages);
        weap.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses);
        weap.WeaponAttributes = new AosWeaponAttributes(newItem, WeaponAttributes);

        // Set hue again because of resource
        weap.Hue = Hue;
        // Set HP/Max again because of durability
        weap.HitPoints = HitPoints;
        weap.MaxHitPoints = MaxHitPoints;
    }

    public int GetDurabilityBonus()
    {
        if (!Core.UOR)
        {
            return (int)_durabilityLevel * 5 + ((int)_quality - 1) * 10;
        }

        var bonus = _durabilityLevel switch
        {
            WeaponDurabilityLevel.Durable        => 20,
            WeaponDurabilityLevel.Substantial    => 50,
            WeaponDurabilityLevel.Massive        => 70,
            WeaponDurabilityLevel.Fortified      => 100,
            WeaponDurabilityLevel.Indestructible => 120,
            _                                    => 0
        };

        if (Core.AOS)
        {
            var resInfo = CraftResources.GetInfo(_resource);
            bonus += WeaponAttributes.DurabilityBonus + (resInfo?.AttributeInfo?.WeaponDurability ?? 0);
        }

        return _quality == WeaponQuality.Exceptional ? bonus + 20 : bonus;
    }

    public int GetLowerStatReq()
    {
        if (!Core.AOS)
        {
            return 0;
        }

        var v = WeaponAttributes.LowerStatReq;

        var attrInfo = CraftResources.GetInfo(_resource)?.AttributeInfo;

        if (attrInfo != null)
        {
            v += attrInfo.WeaponLowerRequirements;
        }

        if (v > 100)
        {
            v = 100;
        }

        return v;
    }

    public static void BlockEquip(Mobile m, TimeSpan duration)
    {
        if (m.BeginAction<BaseWeapon>())
        {
            new ResetEquipTimer(m, duration).Start();
        }
    }

    public override bool CheckConflictingLayer(Mobile m, Item item, Layer layer)
    {
        if (base.CheckConflictingLayer(m, item, layer))
        {
            return true;
        }

        if (Layer == Layer.TwoHanded && layer == Layer.OneHanded)
        {
            m.SendLocalizedMessage(500214); // You already have something in both hands.
            return true;
        }

        if (Layer == Layer.OneHanded && layer == Layer.TwoHanded && item is not BaseShield &&
            item is not BaseEquipableLight)
        {
            m.SendLocalizedMessage(500215); // You can only wield one weapon at a time.
            return true;
        }

        return false;
    }

    public override bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) =>
        Ethic.CheckTrade(from, to, newOwner, this) &&
        base.AllowSecureTrade(from, to, newOwner, accepted);

    public override bool CanEquip(Mobile from)
    {
        if (!from.Player || from.AccessLevel >= AccessLevel.GameMaster)
        {
            return from.CanBeginAction<BaseWeapon>() && base.CanEquip(from);
        }

        if (!Ethic.CheckEquip(from, this))
        {
            return false;
        }

        if (!CheckRace(from))
        {
            return false;
        }

        if (from.Dex < DexRequirement)
        {
            from.SendMessage("You are not nimble enough to equip that.");
            return false;
        }

        if (from.Str < AOS.Scale(StrRequirement, 100 - GetLowerStatReq()))
        {
            from.SendLocalizedMessage(500213); // You are not strong enough to equip that.
            return false;
        }

        if (from.Int < IntRequirement)
        {
            from.SendMessage("You are not smart enough to equip that.");
            return false;
        }

        return from.CanBeginAction<BaseWeapon>() && base.CanEquip(from);
    }

    public override bool OnEquip(Mobile from)
    {
        var strBonus = Attributes.BonusStr;
        var dexBonus = Attributes.BonusDex;
        var intBonus = Attributes.BonusInt;

        if (strBonus != 0 || dexBonus != 0 || intBonus != 0)
        {
            var m = from;

            if (strBonus != 0)
            {
                m.AddStatMod(new StatMod(StatType.Str, $"{Serial}Str", strBonus, TimeSpan.Zero));
            }

            if (dexBonus != 0)
            {
                m.AddStatMod(new StatMod(StatType.Dex, $"{Serial}Dex", dexBonus, TimeSpan.Zero));
            }

            if (intBonus != 0)
            {
                m.AddStatMod(new StatMod(StatType.Int, $"{Serial}Int", intBonus, TimeSpan.Zero));
            }
        }

        if (!_enableInstaHit)
        {
            from.NextCombatTime = Core.TickCount + (int)GetDelay(from).TotalMilliseconds;
        }

        if (UseSkillMod && _accuracyLevel != WeaponAccuracyLevel.Regular)
        {
            m_SkillMod?.Remove();

            m_SkillMod = new DefaultSkillMod(AccuracySkill, "WeaponAccuracy", true, (int)_accuracyLevel * 5);
            from.AddSkillMod(m_SkillMod);
        }

        if (Core.AOS && WeaponAttributes.MageWeapon != 0 && WeaponAttributes.MageWeapon != 30)
        {
            m_MageMod?.Remove();

            m_MageMod = new DefaultSkillMod(SkillName.Magery, "MageWeapon", true, -30 + WeaponAttributes.MageWeapon);
            from.AddSkillMod(m_MageMod);
        }

        return true;
    }

    public override void OnAdded(IEntity parent)
    {
        base.OnAdded(parent);

        if (parent is Mobile from)
        {
            if (Core.AOS)
            {
                SkillBonuses.AddTo(from);
            }

            from.CheckStatTimers();
            from.Delta(MobileDelta.WeaponDamage);
        }
    }

    public override void OnRemoved(IEntity parent)
    {
        if (parent is not Mobile m)
        {
            return;
        }

        var serial = Serial;

        m.RemoveStatMod($"{serial}Str");
        m.RemoveStatMod($"{serial}Dex");
        m.RemoveStatMod($"{serial}Int");

        if (!_enableInstaHit && m.Weapon is BaseWeapon weapon)
        {
            m.NextCombatTime = Core.TickCount + (long)weapon.GetDelay(m).TotalMilliseconds;
        }

        if (UseSkillMod && m_SkillMod != null)
        {
            m_SkillMod.Remove();
            m_SkillMod = null;
        }

        if (m_MageMod != null)
        {
            m_MageMod.Remove();
            m_MageMod = null;
        }

        if (Core.AOS)
        {
            SkillBonuses.Remove();
        }

        ImmolatingWeaponSpell.StopImmolating(this);
        ForceOfNature.Remove(m);

        m.CheckStatTimers();

        m.Delta(MobileDelta.WeaponDamage);
    }

    public virtual SkillName GetUsedSkill(Mobile m, bool checkSkillAttrs)
    {
        SkillName sk;

        if (checkSkillAttrs && WeaponAttributes.UseBestSkill != 0)
        {
            var swrd = m.Skills.Swords.Value;
            var fenc = m.Skills.Fencing.Value;
            var mcng = m.Skills.Macing.Value;
            double val;

            sk = SkillName.Swords;
            val = swrd;

            if (fenc > val)
            {
                sk = SkillName.Fencing;
                val = fenc;
            }

            if (mcng > val)
            {
                sk = SkillName.Macing;
            }
        }
        else if (WeaponAttributes.MageWeapon != 0)
        {
            if (m.Skills.Magery.Value > m.Skills[Skill].Value)
            {
                sk = SkillName.Magery;
            }
            else
            {
                sk = Skill;
            }
        }
        else
        {
            sk = Skill;

            if (sk != SkillName.Wrestling && !m.Player && !m.Body.IsHuman &&
                m.Skills.Wrestling.Value > m.Skills[sk].Value)
            {
                sk = SkillName.Wrestling;
            }
        }

        return sk;
    }

    public virtual double GetAttackSkillValue(Mobile attacker, Mobile defender) =>
        attacker.Skills[GetUsedSkill(attacker, true)].Value;

    public virtual double GetDefendSkillValue(Mobile attacker, Mobile defender) =>
        defender.Skills[GetUsedSkill(defender, true)].Value;

    public virtual bool CheckHit(Mobile attacker, Mobile defender)
    {
        var atkWeapon = attacker.Weapon as BaseWeapon;
        var defWeapon = defender.Weapon as BaseWeapon;

        var atkSkill = attacker.Skills[atkWeapon?.Skill ?? SkillName.Wrestling];

        var atkValue = atkWeapon?.GetAttackSkillValue(attacker, defender) ?? 0.0;
        var defValue = defWeapon?.GetDefendSkillValue(attacker, defender) ?? 0.0;

        double ourValue, theirValue;

        var bonus = GetHitChanceBonus();

        if (Core.AOS)
        {
            if (atkValue <= -20.0)
            {
                atkValue = -19.9;
            }

            if (defValue <= -20.0)
            {
                defValue = -19.9;
            }

            bonus += AosAttributes.GetValue(attacker, AosAttribute.AttackChance);

            if (DivineFurySpell.UnderEffect(attacker))
            {
                bonus += 10; // attacker gets 10% bonus when they're under divine fury
            }

            if (AnimalForm.UnderTransformation(attacker, typeof(GreyWolf)) ||
                AnimalForm.UnderTransformation(attacker, typeof(BakeKitsune)))
            {
                bonus += 20; // attacker gets 20% bonus when under Wolf or Bake Kitsune form
            }

            if (HitLower.IsUnderAttackEffect(attacker))
            {
                bonus -= 25; // Under Hit Lower Attack effect -> 25% malus
            }

            var ability = WeaponAbility.GetCurrentAbility(attacker);

            if (ability != null)
            {
                bonus += ability.AccuracyBonus;
            }

            var move = SpecialMove.GetCurrentMove(attacker);

            if (move != null)
            {
                bonus += move.GetAccuracyBonus(attacker);
            }

            // Max Hit Chance Increase = 45%
            if (bonus > 45)
            {
                bonus = 45;
            }

            ourValue = (atkValue + 20.0) * (100 + bonus);

            bonus = AosAttributes.GetValue(defender, AosAttribute.DefendChance);

            var info = ForceArrow.GetInfo(attacker, defender);

            if (info != null && info.Defender == defender)
            {
                bonus -= info.DefenseChanceMalus;
            }

            if (DivineFurySpell.UnderEffect(defender))
            {
                bonus -= 20; // defender loses 20% bonus when they're under divine fury
            }

            if (HitLower.IsUnderDefenseEffect(defender))
            {
                bonus -= 25; // Under Hit Lower Defense effect -> 25% malus
            }

            var blockBonus = 0;

            if (Block.GetBonus(defender, ref blockBonus))
            {
                bonus += blockBonus;
            }

            var surpriseMalus = 0;

            if (SurpriseAttack.GetMalus(defender, ref surpriseMalus))
            {
                bonus -= surpriseMalus;
            }

            var discordanceEffect = 0;

            // Defender loses -0/-28% if under the effect of Discordance.
            if (Discordance.GetEffect(attacker, ref discordanceEffect))
            {
                bonus -= discordanceEffect;
            }

            // Defense Chance Increase = 45%
            if (bonus > 45)
            {
                bonus = 45;
            }

            theirValue = (defValue + 20.0) * (100 + bonus);

            bonus = 0;
        }
        else
        {
            ourValue = Math.Max(0.1, atkValue + 50.0);
            theirValue = Math.Max(0.1, defValue + 50.0);
        }

        var chance = ourValue / (theirValue * 2.0) * 1.0 + (double)bonus / 100;

        if (Core.AOS && chance < 0.02)
        {
            chance = 0.02;
        }

        return attacker.CheckSkill(atkSkill.SkillName, chance);
    }

    public virtual TimeSpan GetDelay(Mobile m)
    {
        double speed = Speed;

        if (speed == 0)
        {
            return TimeSpan.FromHours(1.0);
        }

        double delayInSeconds;

        if (Core.SE)
        {
            /*
             * This is likely true for Core.AOS as well... both guides report the same
             * formula, and both are wrong.
             * The old formula left in for AOS for legacy & because we aren't quite 100%
             * Sure that AOS has THIS formula
             */
            var bonus = AosAttributes.GetValue(m, AosAttribute.WeaponSpeed);

            bonus += DivineFurySpell.GetWeaponSpeed(m);

            // Bonus granted by successful use of Honorable Execution.
            bonus += HonorableExecution.GetSwingBonus(m);

            if (DualWield.Registry.TryGetValue(m, out var duelWield))
            {
                bonus += duelWield.BonusSwingSpeed;
            }

            var context = TransformationSpellHelper.GetContext(m);

            if (context?.Spell is ReaperFormSpell spell)
            {
                bonus += spell.SwingSpeedBonus;
            }

            var discordanceEffect = 0;

            // Discordance gives a malus of -0/-28% to swing speed.
            if (Discordance.GetEffect(m, ref discordanceEffect))
            {
                bonus -= discordanceEffect;
            }

            if (EssenceOfWindSpell.IsDebuffed(m))
            {
                bonus -= EssenceOfWindSpell.GetSSIMalus(m);
            }

            if (bonus > 60)
            {
                bonus = 60;
            }

            double ticks;

            if (Core.ML)
            {
                var stamTicks = m.Stam / 30;

                ticks = speed * 4;
                ticks = Math.Floor((ticks - stamTicks) * (100.0 / (100 + bonus)));
            }
            else
            {
                speed = Math.Floor(speed * (bonus + 100.0) / 100.0);

                if (speed <= 0)
                {
                    speed = 1;
                }

                ticks = Math.Floor(80000.0 / ((m.Stam + 100) * speed) - 2);
            }

            // Swing speed currently capped at one swing every 1.25 seconds (5 ticks).
            if (ticks < 5)
            {
                ticks = 5;
            }

            delayInSeconds = ticks * 0.25;
        }
        else if (Core.AOS)
        {
            var v = (m.Stam + 100) * (int)speed;

            var bonus = AosAttributes.GetValue(m, AosAttribute.WeaponSpeed);

            if (DivineFurySpell.UnderEffect(m))
            {
                bonus += 10;
            }

            var discordanceEffect = 0;

            // Discordance gives a malus of -0/-28% to swing speed.
            if (Discordance.GetEffect(m, ref discordanceEffect))
            {
                bonus -= discordanceEffect;
            }

            v += AOS.Scale(v, bonus);

            if (v <= 0)
            {
                v = 1;
            }

            delayInSeconds = Math.Floor(40000.0 / v) * 0.5;

            // Maximum swing rate capped at one swing per second
            // OSI dev said that it has and is supposed to be 1.25
            if (delayInSeconds < 1.25)
            {
                delayInSeconds = 1.25;
            }
        }
        else
        {
            var v = (m.Stam + 100) * (int)speed;

            if (v <= 0)
            {
                v = 1;
            }

            delayInSeconds = 15000.0 / v;
        }

        return TimeSpan.FromSeconds(delayInSeconds);
    }

    public static bool CheckParry(Mobile defender)
    {
        if (defender == null)
        {
            return false;
        }

        var shield = defender.FindItemOnLayer<BaseShield>(Layer.TwoHanded);

        var parry = defender.Skills.Parry.Value;
        var bushidoNonRacial = defender.Skills.Bushido.NonRacialValue;
        var bushido = defender.Skills.Bushido.Value;
        double chance;

        if (shield != null)
        {
            // As per OSI, no genitive effect from the Racial stuffs, ie, 120 parry and '0' bushido with humans
            chance = Math.Max((parry - bushidoNonRacial) / 400.0, 0);

            // Parry/Bushido over 100 grants a 5% bonus.
            if (parry >= 100.0 || bushido >= 100.0)
            {
                chance += 0.05;
            }

            // Evasion grants a variable bonus post ML. 50% prior.
            if (Evasion.IsEvading(defender))
            {
                chance *= Evasion.GetParryScalar(defender);
            }

            // Low dexterity lowers the chance.
            if (defender.Dex < 80)
            {
                chance = chance * (20 + defender.Dex) / 100;
            }

            return defender.CheckSkill(SkillName.Parry, chance);
        }

        if (defender.Weapon is Fists or BaseRanged)
        {
            return false;
        }

        var weapon = defender.Weapon as BaseWeapon;

        var divisor = weapon?.Layer == Layer.OneHanded ? 48000.0 : 41140.0;

        chance = parry * bushido / divisor;

        var aosChance = parry / 800.0;

        // Parry or Bushido over 100 grant a 5% bonus.
        if (parry >= 100.0)
        {
            chance += 0.05;
            aosChance += 0.05;
        }
        else if (bushido >= 100.0)
        {
            chance += 0.05;
        }

        // Evasion grants a variable bonus post ML. 50% prior.
        if (Evasion.IsEvading(defender))
        {
            chance *= Evasion.GetParryScalar(defender);
        }

        // Low dexterity lowers the chance.
        if (defender.Dex < 80)
        {
            chance = chance * (20 + defender.Dex) / 100;
        }

        if (chance > aosChance)
        {
            return defender.CheckSkill(SkillName.Parry, chance);
        }

        // Only skillcheck if wielding a shield & there's no effect from Bushido
        return aosChance > Utility.RandomDouble();
    }

    public virtual int AbsorbDamageAOS(Mobile attacker, Mobile defender, int damage)
    {
        var blocked = false;

        if (defender.Player || defender.Body.IsHuman)
        {
            blocked = CheckParry(defender);

            if (blocked)
            {
                defender.FixedEffect(0x37B9, 10, 16);
                damage = 0;

                // Successful block removes the Honorable Execution penalty.
                HonorableExecution.RemovePenalty(defender);

                if (CounterAttack.IsCountering(defender))
                {
                    if (defender.Weapon is BaseWeapon weapon)
                    {
                        defender.FixedParticles(0x3779, 1, 15, 0x158B, 0x0, 0x3, EffectLayer.Waist);
                        weapon.OnSwing(defender, attacker);
                    }

                    CounterAttack.StopCountering(defender);
                }

                if (Confidence.IsConfident(defender))
                {
                    // Your confidence reassures you as you successfully block your opponent's blow.
                    defender.SendLocalizedMessage(1063117);

                    var bushido = defender.Skills.Bushido.Value;

                    defender.Hits += Utility.RandomMinMax(1, (int)(bushido / 12));
                    defender.Stam += Utility.RandomMinMax(1, (int)(bushido / 5));
                }

                var shield = defender.FindItemOnLayer<BaseShield>(Layer.TwoHanded);

                shield?.OnHit(this, damage);
            }
        }

        if (!blocked)
        {
            var positionChance = Utility.RandomDouble();

            var armorItem = positionChance switch
            {
                < 0.07 => defender.NeckArmor,
                < 0.14 => defender.HandArmor,
                < 0.28 => defender.ArmsArmor,
                < 0.43 => defender.HeadArmor,
                < 0.65 => defender.LegsArmor,
                _      => defender.ChestArmor
            };

            if (armorItem is IWearableDurability armor)
            {
                armor.OnHit(this, damage); // call OnHit to lose durability
            }
        }

        return damage;
    }

    public virtual int AbsorbDamage(Mobile attacker, Mobile defender, int damage)
    {
        if (Core.AOS)
        {
            return AbsorbDamageAOS(attacker, defender, damage);
        }

        if (defender.FindItemOnLayer(Layer.TwoHanded) is BaseShield shield)
        {
            damage = shield.OnHit(this, damage);
        }

        var chance = Utility.RandomDouble();

        var armorItem = chance switch
        {
            < 0.07 => defender.NeckArmor,
            < 0.14 => defender.HandArmor,
            < 0.28 => defender.ArmsArmor,
            < 0.43 => defender.HeadArmor,
            < 0.65 => defender.LegsArmor,
            _      => defender.ChestArmor
        };

        if (armorItem is IWearableDurability armor)
        {
            damage = armor.OnHit(this, damage);
        }

        var virtualArmor = defender.VirtualArmor + defender.VirtualArmorMod;

        if (virtualArmor > 0)
        {
            var scalar = chance switch
            {
                < 0.14 => 0.07,
                < 0.28 => 0.14,
                < 0.43 => 0.15,
                < 0.65 => 0.22,
                _      => 0.35
            };

            var from = (int)(virtualArmor * scalar) / 2;
            var to = (int)(virtualArmor * scalar);

            damage -= Utility.Random(from, to - from + 1);
        }

        return damage;
    }

    public virtual int GetPackInstinctBonus(Mobile attacker, Mobile defender)
    {
        if (attacker.Player || defender.Player)
        {
            return 0;
        }

        if (attacker is not BaseCreature bc || bc.PackInstinct == PackInstinct.None || !bc.Controlled && !bc.Summoned)
        {
            return 0;
        }

        var master = bc.ControlMaster ?? bc.SummonMaster;

        if (master == null)
        {
            return 0;
        }

        var inPack = 1;
        foreach (var m in defender.GetMobilesInRange<BaseCreature>(1))
        {
            if (m != attacker && (m.PackInstinct & bc.PackInstinct) != 0 && (m.Controlled || m.Summoned) &&
                master == (m.ControlMaster ?? m.SummonMaster) && m.Combatant == defender)
            {
                inPack++;
            }
        }

        return inPack switch
        {
            >= 5 => 100,
            4    => 75,
            3    => 50,
            2    => 25,
            _    => 0
        };
    }

    public virtual void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1.0)
    {
        if (MirrorImage.HasClone(defender) && defender.Skills.Ninjitsu.Value / 150.0 > Utility.RandomDouble())
        {
            foreach (var m in defender.GetMobilesInRange<Clone>(4))
            {
                if (m?.Summoned == true && m.SummonMaster == defender)
                {
                    // Your attack has been diverted to a nearby mirror image of your target!
                    attacker.SendLocalizedMessage(1063141);
                    // You manage to divert the attack onto one of your nearby mirror images.
                    defender.SendLocalizedMessage(1063140);

                    /*
                     * TODO: What happens if the Clone parries a blow?
                     * And what about if the attacker is using Honorable Execution
                     * and kills it?
                     */

                    defender = m;
                    break;
                }
            }
        }

        PlaySwingAnimation(attacker);
        PlayHurtAnimation(defender);

        attacker.PlaySound(GetHitAttackSound(attacker, defender));
        defender.PlaySound(GetHitDefendSound(attacker, defender));

        var damage = ComputeDamage(attacker, defender);

        /*
         * The following damage bonuses multiply damage by a factor.
         * Capped at x3 (300%).
         */
        var percentageBonus = 0;

        var a = WeaponAbility.GetCurrentAbility(attacker);
        var move = SpecialMove.GetCurrentMove(attacker);

        if (a != null)
        {
            percentageBonus += (int)(a.DamageScalar * 100) - 100;
        }

        if (move != null)
        {
            percentageBonus += (int)(move.GetDamageScalar(attacker, defender) * 100) - 100;
        }

        percentageBonus += (int)(ForceOfNature.GetDamageScalar(attacker, defender) * 100) - 100;

        percentageBonus += (int)(damageBonus * 100) - 100;

        var cs = CheckSlayers(attacker, defender);

        if (cs != CheckSlayerResult.None)
        {
            if (cs == CheckSlayerResult.Slayer)
            {
                defender.FixedEffect(0x37B9, 10, 5);
            }

            percentageBonus += 100;
        }

        if (!attacker.Player)
        {
            if (defender is PlayerMobile pm && pm.EnemyOfOneType != null && pm.EnemyOfOneType != attacker.GetType())
            {
                percentageBonus += 100;
            }
        }
        else if (!defender.Player && attacker is PlayerMobile pm)
        {
            if (pm.WaitingForEnemy)
            {
                pm.EnemyOfOneType = defender.GetType();
                pm.WaitingForEnemy = false;
            }

            if (pm.EnemyOfOneType == defender.GetType())
            {
                defender.FixedEffect(0x37B9, 10, 5, 1160, 0);

                percentageBonus += 50;
            }
        }

        var packInstinctBonus = GetPackInstinctBonus(attacker, defender);

        if (packInstinctBonus != 0)
        {
            percentageBonus += packInstinctBonus;
        }

        if (InDoubleStrike)
        {
            percentageBonus -= 10;
        }

        var context = TransformationSpellHelper.GetContext(defender);

        if ((_slayer == SlayerName.Silver || _slayer2 == SlayerName.Silver) && context?.Spell is NecromancerSpell &&
            context.Type != typeof(HorrificBeastSpell))
        {
            percentageBonus += 25;
        }

        if (attacker is PlayerMobile pmAttacker && !(Core.ML && defender is PlayerMobile))
        {
            if (VirtueSystem.GetVirtues(pmAttacker)?.HonorActive == true && pmAttacker.InRange(defender, 1))
            {
                percentageBonus += 25;
            }

            if (pmAttacker.SentHonorContext != null && pmAttacker.SentHonorContext.Target == defender)
            {
                percentageBonus += pmAttacker.SentHonorContext.PerfectionDamageBonus;
            }
        }

        if (attacker.Talisman is BaseTalisman talisman && talisman.Killer != null)
        {
            percentageBonus += talisman.Killer.DamageBonus(defender);
        }

        percentageBonus = Math.Min(percentageBonus, 300);

        damage = AOS.Scale(damage, 100 + percentageBonus);

        var defLoc = new WorldLocation(defender);
        var bcAtt = attacker as BaseCreature;
        var bcDef = defender as BaseCreature;

        bcAtt?.AlterMeleeDamageTo(defender, ref damage);
        bcDef?.AlterMeleeDamageFrom(attacker, ref damage);

        damage = AbsorbDamage(attacker, defender, damage);

        if (!Core.AOS && damage < 1)
        {
            damage = 1;
        }
        // Parried
        else if (Core.AOS && damage == 0 && a?.Validate(attacker) == true)
        {
            a = null;
            WeaponAbility.ClearCurrentAbility(attacker);
            attacker.SendLocalizedMessage(1061140); // Your attack was parried!
        }

        AddBlood(attacker, defender, damage);

        GetDamageTypes(
            attacker,
            out var phys,
            out var fire,
            out var cold,
            out var pois,
            out var nrgy,
            out var chaos,
            out var direct
        );

        if (Core.ML && this is BaseRanged)
        {
            attacker
                .FindItemOnLayer<BaseQuiver>(Layer.Cloak)
                ?.AlterBowDamage(ref phys, ref fire, ref cold, ref pois, ref nrgy, ref chaos, ref direct);
        }

        if (Consecrated)
        {
            phys = defender.PhysicalResistance;
            fire = defender.FireResistance;
            cold = defender.ColdResistance;
            pois = defender.PoisonResistance;
            nrgy = defender.EnergyResistance;

            int low = phys, type = 0;

            if (fire < low)
            {
                low = fire;
                type = 1;
            }

            if (cold < low)
            {
                low = cold;
                type = 2;
            }

            if (pois < low)
            {
                low = pois;
                type = 3;
            }

            if (nrgy < low)
            {
                type = 4;
            }

            phys = fire = cold = pois = nrgy = chaos = direct = 0;

            if (type == 0)
            {
                phys = 100;
            }
            else if (type == 1)
            {
                fire = 100;
            }
            else if (type == 2)
            {
                cold = 100;
            }
            else if (type == 3)
            {
                pois = 100;
            }
            else
            {
                nrgy = 100;
            }
        }

        // TODO: Scale damage, alongside the leech effects below, to weapon speed.
        if (damage > 0 && ImmolatingWeaponSpell.IsImmolating(this))
        {
            ImmolatingWeaponSpell.DoEffect(this, defender);
        }

        if (a?.OnBeforeDamage(attacker, defender) == false)
        {
            WeaponAbility.ClearCurrentAbility(attacker);
            a = null;
        }

        if (move?.OnBeforeDamage(attacker, defender) == false)
        {
            SpecialMove.ClearCurrentMove(attacker);
            move = null;
        }

        if (Feint.GetDamageReduction(attacker, defender, out var feintReduction))
        {
            // example: 35 damage * 50 / 100 = 17 damage
            damage -= damage * feintReduction / 100;
        }

        var ignoreArmor = a is ArmorIgnore ||
                          move?.IgnoreArmor(attacker) == true ||
                          Bladeweave.BladeWeaving(attacker, out var bladeweavingAbi) &&
                          bladeweavingAbi is ArmorIgnore;

        var damageGiven = AOS.Damage(
            defender,
            attacker,
            damage,
            ignoreArmor,
            phys,
            fire,
            cold,
            pois,
            nrgy,
            chaos,
            direct,
            false,
            this is BaseRanged
        );

        if (damageGiven > 0)
        {
            var propertyBonus = move?.GetPropertyBonus(attacker) ?? 1.0;

            // Leech abilities
            if (Core.AOS)
            {
                var lifeLeech = 0;
                var stamLeech = 0;
                var manaLeech = 0;

                if ((int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitLeechHits) * propertyBonus) >
                    Utility.Random(100))
                {
                    lifeLeech += 30; // HitLeechHits% chance to leech 30% of damage as hit points
                }

                if ((int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitLeechStam) * propertyBonus) >
                    Utility.Random(100))
                {
                    stamLeech += 100; // HitLeechStam% chance to leech 100% of damage as stamina
                }

                if ((int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitLeechMana) * propertyBonus) >
                    Utility.Random(100))
                {
                    manaLeech += 40; // HitLeechMana% chance to leech 40% of damage as mana
                }

                if (Cursed)
                {
                    lifeLeech += 50; // Additional 50% life leech for cursed weapons (necro spell)
                }

                if (lifeLeech != 0)
                {
                    attacker.Hits += AOS.Scale(damageGiven, lifeLeech);
                }

                if (stamLeech != 0)
                {
                    attacker.Stam += AOS.Scale(damageGiven, stamLeech);
                }

                if (manaLeech != 0)
                {
                    attacker.Mana += AOS.Scale(damageGiven, manaLeech);
                }

                if (lifeLeech != 0 || stamLeech != 0 || manaLeech != 0)
                {
                    attacker.PlaySound(0x44D);
                }
            }

            var isAcidMonster =
                _maxHitPoints > 0 && MaxRange <= 1 && Attributes.SpellChanneling == 0 &&
                defender is Slime or AcidElemental;

            // Stratics says 50% chance, seems more like 4%..
            if (isAcidMonster || Utility.Random(25) == 0)
            {
                if (isAcidMonster)
                {
                    attacker.LocalOverheadMessage(MessageType.Regular, 0x3B2, 500263); // *Acid blood scars your weapon!*
                }

                if (Core.AOS && WeaponAttributes.SelfRepair > Utility.Random(10))
                {
                    HitPoints += 2;
                }
                else if (_hitPoints > 0)
                {
                    --HitPoints;
                }
                else if (_maxHitPoints > 1)
                {
                    --MaxHitPoints;

                    if (Parent is Mobile mobile)
                    {
                        // Your equipment is severely damaged.
                        mobile.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1061121);
                    }
                }
                else
                {
                    Delete();
                }
            }

            if (attacker is VampireBatFamiliar bc)
            {
                var caster = bc.ControlMaster ?? bc.SummonMaster;

                if (caster != null && caster.Map == bc.Map && caster.InRange(bc, 2))
                {
                    caster.Hits += damage;
                }
                else
                {
                    bc.Hits += damage;
                }
            }

            if (Core.AOS)
            {
                var physChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitPhysicalArea) * propertyBonus);

                var fireChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitFireArea) * propertyBonus);

                var coldChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitColdArea) * propertyBonus);

                var poisChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitPoisonArea) * propertyBonus);

                var nrgyChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitEnergyArea) * propertyBonus);

                if (physChance != 0 && physChance > Utility.Random(100))
                {
                    DoAreaAttack(attacker, defender, 0x10E, 50, 100, 0, 0, 0, 0);
                }

                if (fireChance != 0 && fireChance > Utility.Random(100))
                {
                    DoAreaAttack(attacker, defender, 0x11D, 1160, 0, 100, 0, 0, 0);
                }

                if (coldChance != 0 && coldChance > Utility.Random(100))
                {
                    DoAreaAttack(attacker, defender, 0x0FC, 2100, 0, 0, 100, 0, 0);
                }

                if (poisChance != 0 && poisChance > Utility.Random(100))
                {
                    DoAreaAttack(attacker, defender, 0x205, 1166, 0, 0, 0, 100, 0);
                }

                if (nrgyChance != 0 && nrgyChance > Utility.Random(100))
                {
                    DoAreaAttack(attacker, defender, 0x1F1, 120, 0, 0, 0, 0, 100);
                }

                var maChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitMagicArrow) * propertyBonus);
                var harmChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitHarm) * propertyBonus);
                var fireballChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitFireball) * propertyBonus);
                var lightningChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitLightning) * propertyBonus);
                var dispelChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitDispel) * propertyBonus);

                if (maChance != 0 && maChance > Utility.Random(100))
                {
                    DoMagicArrow(attacker, defender);
                }

                if (harmChance != 0 && harmChance > Utility.Random(100))
                {
                    DoHarm(attacker, defender);
                }

                if (fireballChance != 0 && fireballChance > Utility.Random(100))
                {
                    DoFireball(attacker, defender);
                }

                if (lightningChance != 0 && lightningChance > Utility.Random(100))
                {
                    DoLightning(attacker, defender);
                }

                if (dispelChance != 0 && dispelChance > Utility.Random(100))
                {
                    DoDispel(attacker, defender);
                }

                var laChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitLowerAttack) * propertyBonus);
                var ldChance =
                    (int)(AosWeaponAttributes.GetValue(attacker, AosWeaponAttribute.HitLowerDefend) * propertyBonus);

                if (laChance != 0 && laChance > Utility.Random(100))
                {
                    DoLowerAttack(attacker, defender);
                }

                if (ldChance != 0 && ldChance > Utility.Random(100))
                {
                    DoLowerDefense(attacker, defender);
                }
            }
        }

        bcAtt?.OnGaveMeleeAttack(defender, damage);
        bcDef?.OnGotMeleeAttack(attacker, damage);

        a?.OnHit(attacker, defender, damage, defLoc);
        move?.OnHit(attacker, defender, damage);

        ForceOfNature.OnHit(attacker, defender);

        if (defender is IHonorTarget it)
        {
            it.ReceivedHonorContext?.OnTargetHit(attacker);
        }

        if (this is not BaseRanged)
        {
            if (AnimalForm.UnderTransformation(attacker, typeof(GiantSerpent)))
            {
                defender.ApplyPoison(attacker, Poison.Lesser);
            }

            if (AnimalForm.UnderTransformation(defender, typeof(BullFrog)))
            {
                attacker.ApplyPoison(defender, Poison.Regular);
            }
        }
    }

    public virtual double GetAosDamage(Mobile attacker, int bonus, int dice, int sides)
    {
        var damage = Utility.Dice(dice, sides, bonus) * 100;

        // Inscription bonus
        var inscribeSkill = attacker.Skills.Inscribe.Fixed;

        var damageBonus = inscribeSkill / 200;

        if (inscribeSkill >= 1000)
        {
            damageBonus += 5;
        }

        if (attacker.Player)
        {
            // Int bonus
            damageBonus += attacker.Int / 10;

            // SDI bonus
            damageBonus += AosAttributes.GetValue(attacker, AosAttribute.SpellDamage);

            if (PsychicAttack.Registry.TryGetValue(attacker, out var timer))
            {
                damageBonus -= timer.SpellDamageMalus;
            }

            var context = TransformationSpellHelper.GetContext(attacker);

            if (context?.Spell is ReaperFormSpell spell)
            {
                damageBonus += spell.SpellDamageBonus;
            }
        }

        damage = AOS.Scale(damage, 100 + damageBonus);

        return damage / 100.0;
    }

    public virtual CheckSlayerResult CheckSlayers(Mobile attacker, Mobile defender)
    {
        if (WeaponAbility.GetCurrentAbility(attacker) is not LightningArrow)
        {
            var atkWeapon = attacker.Weapon as BaseWeapon;
            var atkSlayer = SlayerGroup.GetEntryByName(atkWeapon?.Slayer ?? SlayerName.None);
            var atkSlayer2 = SlayerGroup.GetEntryByName(atkWeapon?.Slayer2 ?? SlayerName.None);

            if (atkWeapon is ButchersWarCleaver && TalismanSlayer.Slays(TalismanSlayerName.Bovine, defender))
            {
                return CheckSlayerResult.Slayer;
            }

            if (atkSlayer?.Slays(defender) == true || atkSlayer2?.Slays(defender) == true)
            {
                return CheckSlayerResult.Slayer;
            }

            if (attacker.Talisman is BaseTalisman talisman && TalismanSlayer.Slays(talisman.Slayer, defender))
            {
                return CheckSlayerResult.Slayer;
            }
        }

        if (!Core.SE)
        {
            var defISlayer = Spellbook.FindEquippedSpellbook(defender) ?? defender.Weapon as ISlayer;

            if (defISlayer != null)
            {
                var defSlayer = SlayerGroup.GetEntryByName(defISlayer.Slayer);
                var defSlayer2 = SlayerGroup.GetEntryByName(defISlayer.Slayer2);

                if (defSlayer?.Group.OppositionSuperSlays(attacker) == true ||
                    defSlayer2?.Group.OppositionSuperSlays(attacker) == true)
                {
                    return CheckSlayerResult.Opposition;
                }
            }
        }

        return CheckSlayerResult.None;
    }

    public virtual void AddBlood(Mobile attacker, Mobile defender, int damage)
    {
        if (damage <= 0)
        {
            return;
        }

        new Blood().MoveToWorld(defender.Location, defender.Map);

        var extraBlood = Core.SE ? Utility.RandomMinMax(3, 4) : Utility.RandomMinMax(0, 1);

        for (var i = 0; i < extraBlood; i++)
        {
            new Blood().MoveToWorld(
                new Point3D(
                    defender.X + Utility.RandomMinMax(-1, 1),
                    defender.Y + Utility.RandomMinMax(-1, 1),
                    defender.Z
                ),
                defender.Map
            );
        }
    }

    public virtual void GetDamageTypes(
        Mobile wielder, out int phys, out int fire, out int cold, out int pois,
        out int nrgy, out int chaos, out int direct
    )
    {
        if (wielder is BaseCreature bc)
        {
            phys = bc.PhysicalDamage;
            fire = bc.FireDamage;
            cold = bc.ColdDamage;
            pois = bc.PoisonDamage;
            nrgy = bc.EnergyDamage;
            chaos = bc.ChaosDamage;
            direct = bc.DirectDamage;
        }
        else
        {
            fire = AosElementDamages.Fire;
            cold = AosElementDamages.Cold;
            pois = AosElementDamages.Poison;
            nrgy = AosElementDamages.Energy;
            chaos = AosElementDamages.Chaos;
            direct = AosElementDamages.Direct;

            phys = 100 - fire - cold - pois - nrgy - chaos - direct;

            var attrInfo = CraftResources.GetInfo(_resource)?.AttributeInfo;

            if (attrInfo != null)
            {
                var left = phys;

                left = ApplyCraftAttributeElementDamage(attrInfo.WeaponColdDamage, ref cold, left);
                left = ApplyCraftAttributeElementDamage(attrInfo.WeaponEnergyDamage, ref nrgy, left);
                left = ApplyCraftAttributeElementDamage(attrInfo.WeaponFireDamage, ref fire, left);
                left = ApplyCraftAttributeElementDamage(attrInfo.WeaponPoisonDamage, ref pois, left);
                left = ApplyCraftAttributeElementDamage(attrInfo.WeaponChaosDamage, ref chaos, left);
                left = ApplyCraftAttributeElementDamage(attrInfo.WeaponDirectDamage, ref direct, left);

                phys = left;
            }
        }
    }

    private int ApplyCraftAttributeElementDamage(int attrDamage, ref int element, int totalRemaining)
    {
        if (totalRemaining <= 0)
        {
            return 0;
        }

        if (attrDamage <= 0)
        {
            return totalRemaining;
        }

        var appliedDamage = attrDamage;

        if (appliedDamage + element > 100)
        {
            appliedDamage = 100 - element;
        }

        if (appliedDamage > totalRemaining)
        {
            appliedDamage = totalRemaining;
        }

        element += appliedDamage;

        return totalRemaining - appliedDamage;
    }

    public virtual void OnMiss(Mobile attacker, Mobile defender)
    {
        PlaySwingAnimation(attacker);
        attacker.PlaySound(GetMissAttackSound(attacker, defender));
        defender.PlaySound(GetMissDefendSound(attacker, defender));

        WeaponAbility.GetCurrentAbility(attacker)?.OnMiss(attacker, defender);
        SpecialMove.GetCurrentMove(attacker)?.OnMiss(attacker, defender);

        if (defender is IHonorTarget target)
        {
            target.ReceivedHonorContext?.OnTargetMissed(attacker);
        }
    }

    public virtual void GetBaseDamageRange(Mobile attacker, out int min, out int max)
    {
        if (attacker is BaseCreature c)
        {
            if (c.DamageMin >= 0)
            {
                min = c.DamageMin;
                max = c.DamageMax;
                return;
            }

            if (this is Fists && !c.Body.IsHuman)
            {
                min = c.Str / 28;
                max = c.Str / 28;
                return;
            }
        }

        min = MinDamage;
        max = MaxDamage;
    }

    public virtual double GetBaseDamage(Mobile attacker)
    {
        GetBaseDamageRange(attacker, out var min, out var max);

        var damage = Utility.RandomMinMax(min, max);

        if (Core.AOS)
        {
            return damage;
        }

        /* Apply damage level offset
         * : Regular : 0
         * : Ruin    : 1
         * : Might   : 3
         * : Force   : 5
         * : Power   : 7
         * : Vanq    : 9
         */
        if (_damageLevel != WeaponDamageLevel.Regular)
        {
            // Toward the end of T2A, calculations were changed
            if (!Core.T2A)
            {
                damage += (int)_damageLevel;
            }
            else
            {
                damage += 2 * (int)_damageLevel - 1;
            }
        }

        return damage;
    }

    public virtual double GetBonus(double value, double scalar, double threshold, double offset)
    {
        var bonus = value * scalar;

        if (value >= threshold)
        {
            bonus += offset;
        }

        return bonus / 100;
    }

    public virtual int GetHitChanceBonus()
    {
        if (!Core.AOS)
        {
            return 0;
        }

        return _accuracyLevel switch
        {
            WeaponAccuracyLevel.Accurate     => 2,
            WeaponAccuracyLevel.Surpassingly => 4,
            WeaponAccuracyLevel.Eminently    => 6,
            WeaponAccuracyLevel.Exceedingly  => 8,
            WeaponAccuracyLevel.Supremely    => 10,
            _                                => 0
        };
    }

    // Note: AOS quality/damage bonuses removed since they are incorporated into the crafting already
    public virtual int GetDamageBonus() => VirtualDamageBonus;

    public virtual double ScaleDamageAOS(Mobile attacker, double damage, bool checkSkills)
    {
        if (checkSkills)
        {
            // Passively check tactics for gain
            attacker.CheckSkill(SkillName.Tactics, 0.0, attacker.Skills.Tactics.Cap);

            // Passively check Anatomy for gain
            attacker.CheckSkill(SkillName.Anatomy, 0.0, attacker.Skills.Anatomy.Cap);

            if (Type == WeaponType.Axe)
            {
                // Passively check Lumberjacking for gain
                attacker.CheckSkill(SkillName.Lumberjacking, 0.0, 100.0);
            }
        }

        /*
         * These are the bonuses given by the physical characteristics of the mobile.
         * No caps apply.
         */
        var strengthBonus = GetBonus(attacker.Str, 0.300, 100.0, 5.00);
        var anatomyBonus = GetBonus(attacker.Skills.Anatomy.Value, 0.500, 100.0, 5.00);
        var tacticsBonus = GetBonus(attacker.Skills.Tactics.Value, 0.625, 100.0, 6.25);
        var lumberBonus = Type == WeaponType.Axe
            ? GetBonus(attacker.Skills.Lumberjacking.Value, 0.200, 100.0, 10.00)
            : 0.0;

        /*
         * The following are damage modifiers whose effect shows on the status bar.
         * Capped at 100% total.
         */
        var damageBonus = AosAttributes.GetValue(attacker, AosAttribute.WeaponDamage);

        // Horrific Beast transformation gives a +25% bonus to damage.
        if (TransformationSpellHelper.UnderTransformation(attacker, typeof(HorrificBeastSpell)))
        {
            damageBonus += 25;
        }

        // Divine Fury gives a +10% bonus to damage.
        damageBonus += DivineFurySpell.GetDamageBonus(attacker);

        var defenseMasteryMalus = 0;

        // Defense Mastery gives a -50%/-80% malus to damage.
        if (DefenseMastery.GetMalus(attacker, ref defenseMasteryMalus))
        {
            damageBonus -= defenseMasteryMalus;
        }

        var discordanceEffect = 0;

        // Discordance gives a -2%/-48% malus to damage.
        if (Discordance.GetEffect(attacker, ref discordanceEffect))
        {
            damageBonus -= discordanceEffect * 2;
        }

        if (damageBonus > 100)
        {
            damageBonus = 100;
        }

        var totalBonus = strengthBonus + anatomyBonus + tacticsBonus + lumberBonus +
                         (damageBonus + GetDamageBonus()) / 100.0;

        return damage + damage * totalBonus;
    }

    public virtual int ComputeDamageAOS(Mobile attacker, Mobile defender) =>
        (int)ScaleDamageAOS(attacker, GetBaseDamage(attacker), true);

    public virtual double ScaleDamageOld(Mobile attacker, double damage, bool checkSkills)
    {
        if (checkSkills)
        {
            // Passively check tactics for gain
            attacker.CheckSkill(SkillName.Tactics, 0.0, attacker.Skills.Tactics.Cap);

            // Passively check Anatomy for gain
            attacker.CheckSkill(SkillName.Anatomy, 0.0, attacker.Skills.Anatomy.Cap);

            if (Type == WeaponType.Axe)
            {
                // Passively check Lumberjacking for gain
                attacker.CheckSkill(SkillName.Lumberjacking, 0.0, 100.0);
            }
        }

        /* Compute tactics modifier
         * :   0.0 = 50% loss
         * :  50.0 = unchanged
         * : 100.0 = 50% bonus
         */
        damage += damage * ((attacker.Skills.Tactics.Value - 50.0) / 100.0);

        /* Compute strength modifier
         * : 1% bonus for every 5 strength
         */
        var modifiers = attacker.Str / 5.0 / 100.0;

        /* Compute anatomy modifier
         * : 1% bonus for every 5 points of anatomy
         * : +10% bonus at Grandmaster or higher
         */
        var anatomyValue = attacker.Skills.Anatomy.Value;
        modifiers += anatomyValue / 5.0 / 100.0;

        if (anatomyValue >= 100.0)
        {
            modifiers += 0.1;
        }

        /* Compute lumberjacking bonus
         * : 1% bonus for every 5 points of lumberjacking
         * : +10% bonus at Grandmaster or higher
         */
        if (Type == WeaponType.Axe)
        {
            var lumberValue = attacker.Skills.Lumberjacking.Value;

            modifiers += lumberValue / 5.0 / 100.0;

            if (lumberValue >= 100.0)
            {
                modifiers += 0.1;
            }
        }

        // New quality bonus:
        if (_quality != WeaponQuality.Regular)
        {
            modifiers += ((int)_quality - 1) * 0.2;
        }

        // Virtual damage bonus:
        if (VirtualDamageBonus != 0)
        {
            modifiers += VirtualDamageBonus / 100.0;
        }

        // Apply bonuses
        damage += damage * modifiers;

        return ScaleDamageByDurability((int)damage);
    }

    public virtual int ScaleDamageByDurability(int damage)
    {
        var scale = 100;

        if (_maxHitPoints > 0 && _hitPoints < _maxHitPoints)
        {
            scale = 50 + 50 * _hitPoints / _maxHitPoints;
        }

        return AOS.Scale(damage, scale);
    }

    public virtual int ComputeDamage(Mobile attacker, Mobile defender)
    {
        if (Core.AOS)
        {
            return ComputeDamageAOS(attacker, defender);
        }

        var damage = (int)ScaleDamageOld(attacker, GetBaseDamage(attacker), true);

        // pre-AOS, halve damage if the defender is a player or the attacker is not a player
        if (defender is PlayerMobile || attacker is not PlayerMobile)
        {
            damage /= 2;
        }

        return damage;
    }

    public virtual void PlayHurtAnimation(Mobile from)
    {
        if (from.Mounted)
        {
            return;
        }

        int action;
        int frames;

        switch (from.Body.Type)
        {
            case BodyType.Sea:
            case BodyType.Animal:
                {
                    action = 7;
                    frames = 5;
                    break;
                }
            case BodyType.Monster:
                {
                    action = 10;
                    frames = 4;
                    break;
                }
            case BodyType.Human:
                {
                    action = 20;
                    frames = 5;
                    break;
                }
            default:
                {
                    return;
                }
        }

        from.Animate(action, frames, 1, true, false, 0);
    }

    public virtual void PlaySwingAnimation(Mobile from)
    {
        int action;

        switch (from.Body.Type)
        {
            case BodyType.Sea:
            case BodyType.Animal:
                {
                    action = Utility.Random(5, 2);
                    break;
                }
            case BodyType.Monster:
                {
                    switch (Animation)
                    {
                        default:
                            {
                                action = Utility.Random(4, 3);
                                break;
                            }
                        case WeaponAnimation.ShootBow:
                            {
                                return; // 7
                            }
                        case WeaponAnimation.ShootXBow:
                            {
                                return; // 8
                            }
                    }

                    break;
                }
            case BodyType.Human:
                {
                    if (!from.Mounted)
                    {
                        action = (int)Animation;
                    }
                    else
                    {
                        action = Animation switch
                        {
                            WeaponAnimation.Wrestle   => 26,
                            WeaponAnimation.Bash1H    => 26,
                            WeaponAnimation.Pierce1H  => 26,
                            WeaponAnimation.Slash1H   => 26,
                            WeaponAnimation.Bash2H    => 29,
                            WeaponAnimation.Pierce2H  => 29,
                            WeaponAnimation.Slash2H   => 29,
                            WeaponAnimation.ShootBow  => 27,
                            WeaponAnimation.ShootXBow => 28,
                            _                         => 26
                        };
                    }

                    break;
                }
            default:
                {
                    return;
                }
        }

        from.Animate(action, 7, 1, true, false, 0);
    }

    public int GetElementalDamageHue()
    {
        GetDamageTypes(null, out _, out var fire, out var cold, out var pois, out var nrgy, out _, out _);

        var currentMax = 50;
        var hue = 0;

        // Order is Cold, Energy, Fire, Poison, Physical
        if (pois >= currentMax)
        {
            hue = 1267 + (pois - 50) / 10;
            currentMax = pois;
        }

        if (fire >= currentMax)
        {
            hue = 1255 + (fire - 50) / 10;
            currentMax = fire;
        }

        if (nrgy >= currentMax)
        {
            hue = 1273 + (nrgy - 50) / 10;
            currentMax = nrgy;
        }

        if (cold >= currentMax)
        {
            hue = 1261 + (cold - 50) / 10;
        }

        return hue;
    }

    public override void AddNameProperty(IPropertyList list)
    {
        var oreType = _resource switch
        {
            CraftResource.DullCopper    => 1053108,
            CraftResource.ShadowIron    => 1053107,
            CraftResource.Copper        => 1053106,
            CraftResource.Bronze        => 1053105,
            CraftResource.Gold          => 1053104,
            CraftResource.Agapite       => 1053103,
            CraftResource.Verite        => 1053102,
            CraftResource.Valorite      => 1053101,
            CraftResource.SpinedLeather => 1061118,
            CraftResource.HornedLeather => 1061117,
            CraftResource.BarbedLeather => 1061116,
            CraftResource.RedScales     => 1060814,
            CraftResource.YellowScales  => 1060818,
            CraftResource.BlackScales   => 1060820,
            CraftResource.GreenScales   => 1060819,
            CraftResource.WhiteScales   => 1060821,
            CraftResource.BlueScales    => 1060815,
            _                           => 0
        };

        var name = Name;

        if (oreType != 0)
        {
            if (name != null)
            {
                list.Add(1053099, $"{oreType:#}\t{name}"); // ~1_oretype~ ~2_armortype~
            }
            else
            {
                list.Add(1053099, $"{oreType:#}\t{LabelNumber:#}"); // ~1_oretype~ ~2_armortype~
            }
        }
        else if (name == null)
        {
            list.Add(LabelNumber);
        }
        else
        {
            list.Add(name);
        }

        /*
         * Want to move this to the engraving tool, let the non-harmful
         * formatting show, and remove CLILOCs embedded: more like OSI
         * did with the books that had markup, etc.
         *
         * This will have a negative effect on a few event things in-game
         * as is.
         *
         * If we cant find a more OSI-ish way to clean it up, we can
         * easily put this back, and use it in the deserialize
         * method and engraving tool, to make it perm cleaned up.
         */

        if (!string.IsNullOrEmpty(_engravedText))
        {
            list.Add(1062613, _engravedText);
        }

        /* list.Add( 1062613, Utility.FixHtml( m_EngravedText ) ); */
    }

    public override bool AllowEquippedCast(Mobile from) =>
        base.AllowEquippedCast(from) || Attributes.SpellChanneling != 0;

    public virtual int GetLuckBonus() => CraftResources.GetInfo(_resource)?.AttributeInfo?.WeaponLuck ?? 0;

    public override void GetProperties(IPropertyList list)
    {
        base.GetProperties(list);

        if (_crafter != null)
        {
            list.Add(1050043, _crafter); // crafted by ~1_NAME~
        }

        if (m_FactionState != null)
        {
            list.Add(1041350); // faction item
        }

        SkillBonuses?.GetProperties(list);

        if (_quality == WeaponQuality.Exceptional)
        {
            list.Add(1060636); // exceptional
        }

        if (RequiredRaces == Race.AllowElvesOnly)
        {
            list.Add(1075086); // Elves Only
        }

        if (RequiredRaces == Race.AllowGargoylesOnly)
        {
            list.Add(1111709); // Gargoyles Only
        }

        if (ArtifactRarity > 0)
        {
            list.Add(1061078, ArtifactRarity); // artifact rarity ~1_val~
        }

        if (this is IUsesRemaining usesRemaining && usesRemaining.ShowUsesRemaining)
        {
            list.Add(1060584, usesRemaining.UsesRemaining); // uses remaining: ~1_val~
        }

        if (_poison != null && _poisonCharges > 0)
        {
            list.Add(1062412 + _poison.Level, _poisonCharges);
        }

        if (_slayer != SlayerName.None)
        {
            var entry = SlayerGroup.GetEntryByName(_slayer);
            if (entry != null)
            {
                list.Add(entry.Title);
            }
        }

        if (_slayer2 != SlayerName.None)
        {
            var entry = SlayerGroup.GetEntryByName(_slayer2);
            if (entry != null)
            {
                list.Add(entry.Title);
            }
        }

        AddResistanceProperties(list);

        int prop;

        var ranged = this as BaseRanged;

        if (Core.ML && ranged?.Balanced == true)
        {
            list.Add(1072792); // Balanced
        }

        if (WeaponAttributes.UseBestSkill != 0)
        {
            list.Add(1060400); // use best weapon skill
        }

        if ((prop = GetDamageBonus() + Attributes.WeaponDamage) != 0)
        {
            list.Add(1060401, prop); // damage increase ~1_val~%
        }

        if ((prop = Attributes.DefendChance) != 0)
        {
            list.Add(1060408, prop); // defense chance increase ~1_val~%
        }

        if ((prop = Attributes.EnhancePotions) != 0)
        {
            list.Add(1060411, prop); // enhance potions ~1_val~%
        }

        if ((prop = Attributes.CastRecovery) != 0)
        {
            list.Add(1060412, prop); // faster cast recovery ~1_val~
        }

        if ((prop = Attributes.CastSpeed) != 0)
        {
            list.Add(1060413, prop); // faster casting ~1_val~
        }

        if ((prop = GetHitChanceBonus() + Attributes.AttackChance) != 0)
        {
            list.Add(1060415, prop); // hit chance increase ~1_val~%
        }

        if ((prop = WeaponAttributes.HitColdArea) != 0)
        {
            list.Add(1060416, prop); // hit cold area ~1_val~%
        }

        if ((prop = WeaponAttributes.HitDispel) != 0)
        {
            list.Add(1060417, prop); // hit dispel ~1_val~%
        }

        if ((prop = WeaponAttributes.HitEnergyArea) != 0)
        {
            list.Add(1060418, prop); // hit energy area ~1_val~%
        }

        if ((prop = WeaponAttributes.HitFireArea) != 0)
        {
            list.Add(1060419, prop); // hit fire area ~1_val~%
        }

        if ((prop = WeaponAttributes.HitFireball) != 0)
        {
            list.Add(1060420, prop); // hit fireball ~1_val~%
        }

        if ((prop = WeaponAttributes.HitHarm) != 0)
        {
            list.Add(1060421, prop); // hit harm ~1_val~%
        }

        if ((prop = WeaponAttributes.HitLeechHits) != 0)
        {
            list.Add(1060422, prop); // hit life leech ~1_val~%
        }

        if ((prop = WeaponAttributes.HitLightning) != 0)
        {
            list.Add(1060423, prop); // hit lightning ~1_val~%
        }

        if ((prop = WeaponAttributes.HitLowerAttack) != 0)
        {
            list.Add(1060424, prop); // hit lower attack ~1_val~%
        }

        if ((prop = WeaponAttributes.HitLowerDefend) != 0)
        {
            list.Add(1060425, prop); // hit lower defense ~1_val~%
        }

        if ((prop = WeaponAttributes.HitMagicArrow) != 0)
        {
            list.Add(1060426, prop); // hit magic arrow ~1_val~%
        }

        if ((prop = WeaponAttributes.HitLeechMana) != 0)
        {
            list.Add(1060427, prop); // hit mana leech ~1_val~%
        }

        if ((prop = WeaponAttributes.HitPhysicalArea) != 0)
        {
            list.Add(1060428, prop); // hit physical area ~1_val~%
        }

        if ((prop = WeaponAttributes.HitPoisonArea) != 0)
        {
            list.Add(1060429, prop); // hit poison area ~1_val~%
        }

        if ((prop = WeaponAttributes.HitLeechStam) != 0)
        {
            list.Add(1060430, prop); // hit stamina leech ~1_val~%
        }

        if (ImmolatingWeaponSpell.IsImmolating(this))
        {
            list.Add(1111917); // Immolated
        }

        if (Core.ML && (prop = ranged?.Velocity ?? 0) != 0)
        {
            list.Add(1072793, prop); // Velocity ~1_val~%
        }

        if ((prop = Attributes.BonusDex) != 0)
        {
            list.Add(1060409, prop); // dexterity bonus ~1_val~
        }

        if ((prop = Attributes.BonusHits) != 0)
        {
            list.Add(1060431, prop); // hit point increase ~1_val~
        }

        if ((prop = Attributes.BonusInt) != 0)
        {
            list.Add(1060432, prop); // intelligence bonus ~1_val~
        }

        if ((prop = Attributes.LowerManaCost) != 0)
        {
            list.Add(1060433, prop); // lower mana cost ~1_val~%
        }

        if ((prop = Attributes.LowerRegCost) != 0)
        {
            list.Add(1060434, prop); // lower reagent cost ~1_val~%
        }

        if ((prop = GetLowerStatReq()) != 0)
        {
            list.Add(1060435, prop); // lower requirements ~1_val~%
        }

        if ((prop = GetLuckBonus() + Attributes.Luck) != 0)
        {
            list.Add(1060436, prop); // luck ~1_val~
        }

        if ((prop = WeaponAttributes.MageWeapon) != 0)
        {
            list.Add(1060438, 30 - prop); // mage weapon -~1_val~ skill
        }

        if ((prop = Attributes.BonusMana) != 0)
        {
            list.Add(1060439, prop); // mana increase ~1_val~
        }

        if ((prop = Attributes.RegenMana) != 0)
        {
            list.Add(1060440, prop); // mana regeneration ~1_val~
        }

        if (Attributes.NightSight != 0)
        {
            list.Add(1060441); // night sight
        }

        if ((prop = Attributes.ReflectPhysical) != 0)
        {
            list.Add(1060442, prop); // reflect physical damage ~1_val~%
        }

        if ((prop = Attributes.RegenStam) != 0)
        {
            list.Add(1060443, prop); // stamina regeneration ~1_val~
        }

        if ((prop = Attributes.RegenHits) != 0)
        {
            list.Add(1060444, prop); // hit point regeneration ~1_val~
        }

        if ((prop = WeaponAttributes.SelfRepair) != 0)
        {
            list.Add(1060450, prop); // self repair ~1_val~
        }

        if (Attributes.SpellChanneling != 0)
        {
            list.Add(1060482); // spell channeling
        }

        if ((prop = Attributes.SpellDamage) != 0)
        {
            list.Add(1060483, prop); // spell damage increase ~1_val~%
        }

        if ((prop = Attributes.BonusStam) != 0)
        {
            list.Add(1060484, prop); // stamina increase ~1_val~
        }

        if ((prop = Attributes.BonusStr) != 0)
        {
            list.Add(1060485, prop); // strength bonus ~1_val~
        }

        if ((prop = Attributes.WeaponSpeed) != 0)
        {
            list.Add(1060486, prop); // swing speed increase ~1_val~%
        }

        if (Core.ML && (prop = Attributes.IncreasedKarmaLoss) != 0)
        {
            list.Add(1075210, prop); // Increased Karma Loss ~1val~%
        }

        GetDamageTypes(
            null,
            out var phys,
            out var fire,
            out var cold,
            out var pois,
            out var nrgy,
            out var chaos,
            out var direct
        );

        if (phys != 0)
        {
            list.Add(1060403, phys); // physical damage ~1_val~%
        }

        if (fire != 0)
        {
            list.Add(1060405, fire); // fire damage ~1_val~%
        }

        if (cold != 0)
        {
            list.Add(1060404, cold); // cold damage ~1_val~%
        }

        if (pois != 0)
        {
            list.Add(1060406, pois); // poison damage ~1_val~%
        }

        if (nrgy != 0)
        {
            list.Add(1060407, nrgy); // energy damage ~1_val
        }

        if (Core.ML && chaos != 0)
        {
            list.Add(1072846, chaos); // chaos damage ~1_val~%
        }

        if (Core.ML && direct != 0)
        {
            list.Add(1079978, direct); // Direct Damage: ~1_PERCENT~%
        }

        list.Add(1061168, $"{MinDamage}\t{MaxDamage}"); // weapon damage ~1_val~ - ~2_val~

        if (Core.ML)
        {
            list.Add(1061167, $"{Speed}s"); // weapon speed ~1_val~
        }
        else
        {
            list.Add(1061167, $"{Speed}");
        }

        if (MaxRange > 1)
        {
            list.Add(1061169, MaxRange); // range ~1_val~
        }

        var strReq = AOS.Scale(StrRequirement, 100 - GetLowerStatReq());

        if (strReq > 0)
        {
            list.Add(1061170, strReq); // strength requirement ~1_val~
        }

        if (Layer == Layer.TwoHanded)
        {
            list.Add(1061171); // two-handed weapon
        }
        else
        {
            list.Add(1061824); // one-handed weapon
        }

        if (Core.SE || WeaponAttributes.UseBestSkill == 0)
        {
            switch (Skill)
            {
                case SkillName.Swords:
                    {
                        list.Add(1061172); // skill required: swordsmanship
                        break;
                    }
                case SkillName.Macing:
                    {
                        list.Add(1061173); // skill required: mace fighting
                        break;
                    }
                case SkillName.Fencing:
                    {
                        list.Add(1061174); // skill required: fencing
                        break;
                    }
                case SkillName.Archery:
                    {
                        list.Add(1061175); // skill required: archery
                        break;
                    }
            }
        }

        if (_hitPoints >= 0 && _maxHitPoints > 0)
        {
            list.Add(1060639, $"{_hitPoints}\t{_maxHitPoints}"); // durability ~1_val~ / ~2_val~
        }
    }

    public override void OnSingleClick(Mobile from)
    {
        if (!Core.UOTD)
        {
            OnSingleClickPreUOTD(from);
            return;
        }

        var attrs = new List<EquipInfoAttribute>();

        if (DisplayLootType)
        {
            if (LootType == LootType.Blessed)
            {
                attrs.Add(new EquipInfoAttribute(1038021)); // blessed
            }
            else if (LootType == LootType.Cursed)
            {
                attrs.Add(new EquipInfoAttribute(1049643)); // cursed
            }
        }

        if (m_FactionState != null)
        {
            attrs.Add(new EquipInfoAttribute(1041350)); // faction item
        }

        if (_quality == WeaponQuality.Exceptional)
        {
            attrs.Add(new EquipInfoAttribute(1018305 - (int)_quality));
        }

        if (_identified || from.AccessLevel >= AccessLevel.GameMaster)
        {
            if (_slayer != SlayerName.None)
            {
                var entry = SlayerGroup.GetEntryByName(_slayer);
                if (entry != null)
                {
                    attrs.Add(new EquipInfoAttribute(entry.Title));
                }
            }

            if (_slayer2 != SlayerName.None)
            {
                var entry = SlayerGroup.GetEntryByName(_slayer2);
                if (entry != null)
                {
                    attrs.Add(new EquipInfoAttribute(entry.Title));
                }
            }

            if (_durabilityLevel != WeaponDurabilityLevel.Regular)
            {
                attrs.Add(new EquipInfoAttribute(1038000 + (int)_durabilityLevel));
            }

            if (_damageLevel != WeaponDamageLevel.Regular)
            {
                attrs.Add(new EquipInfoAttribute(1038015 + (int)_damageLevel));
            }

            if (_accuracyLevel != WeaponAccuracyLevel.Regular)
            {
                attrs.Add(new EquipInfoAttribute(1038010 + (int)_accuracyLevel));
            }
        }
        else if (_slayer != SlayerName.None || _slayer2 != SlayerName.None ||
                 _durabilityLevel != WeaponDurabilityLevel.Regular || _damageLevel != WeaponDamageLevel.Regular ||
                 _accuracyLevel != WeaponAccuracyLevel.Regular)
        {
            attrs.Add(new EquipInfoAttribute(1038000)); // Unidentified
        }

        if (_poison != null && _poisonCharges > 0)
        {
            attrs.Add(new EquipInfoAttribute(1017383, _poisonCharges));
        }

        int number;

        if (Name == null)
        {
            number = LabelNumber;
        }
        else
        {
            LabelTo(from, Name);
            number = 1041000;
        }

        if (attrs.Count == 0 && Crafter == null && Name != null)
        {
            return;
        }

        from.NetState.SendDisplayEquipmentInfo(Serial, number, _crafter, false, attrs);
    }

    public virtual void OnSingleClickPreUOTD(Mobile from)
    {
        var isMagicItem = _durabilityLevel > WeaponDurabilityLevel.Regular ||
                          _accuracyLevel > WeaponAccuracyLevel.Regular ||
                          _damageLevel > WeaponDamageLevel.Regular ||
                          _slayer != SlayerName.None;

        if (isMagicItem && !_identified)
        {
            LabelTo(from, $"an unidentified {Name ?? Localization.GetText(LabelNumber).ToLowerInvariant()}");
            return;
        }

        var name = Name;
        var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0;

        if (isMagicItem)
        {
            var builder = ValueStringBuilder.Create(128);

            var durabilityText = DurabilityText(out var articleAnDurability);
            if (durabilityText != null)
            {
                builder.AppendSpaceWithArticle(durabilityText, articleAnDurability);
            }

            var accuracyText = AccuracyText(out var articleAnAccuracy);
            if (accuracyText != null)
            {
                builder.AppendSpaceWithArticle(accuracyText, articleAnAccuracy);
            }

            var slayerEntry = SlayerGroup.GetEntryByName(_slayer);
            if (slayerEntry != null)
            {
                builder.AppendSpaceWithArticle(slayerEntry.SlayerText(out var articleAnSlayer), articleAnSlayer);
            }

            if (name == null)
            {
                builder.AppendSpaceWithArticle(Localization.GetText(LabelNumber).ToLowerInvariant(), articleAnName);
            }
            else if (builder.Length != 0)
            {
                builder.Append($" {name}");
            }
            else
            {
                builder.Append(name);
            }

            var weaponDamageText = WeaponDamageText;
            if (weaponDamageText != null)
            {
                builder.Append($" of {weaponDamageText}");
            }

            // TODO: Spells (of Ghoul's Touch)

            LabelTo(from, builder.ToString());
            builder.Dispose();
            return;
        }

        name ??= $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}";

        if (Crafter == null)
        {
            LabelTo(from, Quality == WeaponQuality.Exceptional ? $"{name} of exceptional quality" : name);
            return;
        }

        LabelTo(
            from,
            Quality == WeaponQuality.Exceptional
                ? $"{name} crafted with exceptional quality by {Crafter}"
                : $"{name} crafted by {Crafter}"
        );
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private string DurabilityText(out bool articleAn)
    {
        articleAn = _durabilityLevel is WeaponDurabilityLevel.Indestructible;
        return _durabilityLevel switch
        {
            WeaponDurabilityLevel.Durable        => "durable",
            WeaponDurabilityLevel.Substantial    => "substantial",
            WeaponDurabilityLevel.Massive        => "massive",
            WeaponDurabilityLevel.Fortified      => "fortified",
            WeaponDurabilityLevel.Indestructible => "indestructible",
            _                                    => null
        };
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private string AccuracyText(out bool articleAn)
    {
        articleAn = _accuracyLevel is WeaponAccuracyLevel.Accurate or WeaponAccuracyLevel.Eminently;
        return _accuracyLevel switch
        {
            WeaponAccuracyLevel.Accurate     => "accurate",
            WeaponAccuracyLevel.Surpassingly => "surpassingly accurate",
            WeaponAccuracyLevel.Eminently    => "eminently accurate",
            WeaponAccuracyLevel.Exceedingly  => "exceedingly accurate",
            WeaponAccuracyLevel.Supremely    => "supremely accurate",
            _                                => null
        };
    }

    private string WeaponDamageText
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => _damageLevel switch
        {
            WeaponDamageLevel.Ruin  => "ruin",
            WeaponDamageLevel.Might => "might",
            WeaponDamageLevel.Force => "force",
            WeaponDamageLevel.Power => "power",
            WeaponDamageLevel.Vanq  => "vanquishing",
            _                       => null
        };
    }

    public virtual int GetHitAttackSound(Mobile attacker, Mobile defender)
    {
        var sound = attacker.GetAttackSound();
        return sound == -1 ? HitSound : sound;
    }

    public virtual int GetHitDefendSound(Mobile attacker, Mobile defender) => defender.GetHurtSound();

    public virtual int GetMissAttackSound(Mobile attacker, Mobile defender) =>
        attacker.GetAttackSound() == -1 ? MissSound : -1;

    public virtual int GetMissDefendSound(Mobile attacker, Mobile defender) => -1;

    public virtual void DoMagicArrow(Mobile attacker, Mobile defender)
    {
        if (!attacker.CanBeHarmful(defender, false))
        {
            return;
        }

        attacker.DoHarmful(defender);

        var damage = GetAosDamage(attacker, 10, 1, 4);

        attacker.MovingParticles(defender, 0x36E4, 5, 0, false, true, 3006, 4006, 0);
        attacker.PlaySound(0x1E5);

        SpellHelper.Damage(TimeSpan.FromSeconds(1.0), defender, attacker, damage, 0, 100, 0, 0, 0);
    }

    public virtual void DoHarm(Mobile attacker, Mobile defender)
    {
        if (!attacker.CanBeHarmful(defender, false))
        {
            return;
        }

        attacker.DoHarmful(defender);

        var damage = GetAosDamage(attacker, 17, 1, 5);

        if (!defender.InRange(attacker, 2))
        {
            damage *= 0.25; // 1/4 damage at > 2 tile range
        }
        else if (!defender.InRange(attacker, 1))
        {
            damage *= 0.50; // 1/2 damage at 2 tile range
        }

        defender.FixedParticles(0x374A, 10, 30, 5013, 1153, 2, EffectLayer.Waist);
        defender.PlaySound(0x0FC);

        SpellHelper.Damage(TimeSpan.Zero, defender, attacker, damage, 0, 0, 100, 0, 0);
    }

    public virtual void DoFireball(Mobile attacker, Mobile defender)
    {
        if (!attacker.CanBeHarmful(defender, false))
        {
            return;
        }

        attacker.DoHarmful(defender);

        var damage = GetAosDamage(attacker, 19, 1, 5);

        attacker.MovingParticles(defender, 0x36D4, 7, 0, false, true, 9502, 4019, 0x160);
        attacker.PlaySound(0x15E);

        SpellHelper.Damage(TimeSpan.FromSeconds(1.0), defender, attacker, damage, 0, 100, 0, 0, 0);
    }

    public virtual void DoLightning(Mobile attacker, Mobile defender)
    {
        if (!attacker.CanBeHarmful(defender, false))
        {
            return;
        }

        attacker.DoHarmful(defender);

        var damage = GetAosDamage(attacker, 23, 1, 4);

        defender.BoltEffect(0);

        SpellHelper.Damage(TimeSpan.Zero, defender, attacker, damage, 0, 0, 0, 0, 100);
    }

    public virtual void DoDispel(Mobile attacker, Mobile defender)
    {
        var dispellable = false;

        if (defender is BaseCreature creature)
        {
            dispellable = creature.Summoned && !creature.IsAnimatedDead;
        }

        if (!dispellable)
        {
            return;
        }

        if (!attacker.CanBeHarmful(defender, false))
        {
            return;
        }

        attacker.DoHarmful(defender);

        var sp = new DispelSpell(attacker);

        if (sp.CheckResisted(defender))
        {
            defender.FixedEffect(0x3779, 10, 20);
        }
        else
        {
            Effects.SendLocationParticles(
                EffectItem.Create(defender.Location, defender.Map, EffectItem.DefaultDuration),
                0x3728,
                8,
                20,
                5042
            );
            Effects.PlaySound(defender, 0x201);

            defender.Delete();
        }
    }

    public virtual void DoLowerAttack(Mobile from, Mobile defender)
    {
        if (HitLower.ApplyAttack(defender))
        {
            defender.PlaySound(0x28E);
            Effects.SendTargetEffect(defender, 0x37BE, 1, 4, 0xA, 3);
        }
    }

    public virtual void DoLowerDefense(Mobile from, Mobile defender)
    {
        if (HitLower.ApplyDefense(defender))
        {
            defender.PlaySound(0x28E);
            Effects.SendTargetEffect(defender, 0x37BE, 1, 4, 0x23, 3);
        }
    }

    public virtual void DoAreaAttack(
        Mobile from, Mobile defender, int sound, int hue, int phys, int fire, int cold,
        int pois, int nrgy
    )
    {
        var map = from.Map;

        if (map == null)
        {
            return;
        }

        using var queue = PooledRefQueue<Mobile>.Create();
        foreach (var m in from.GetMobilesInRange(Core.ML ? 5 : 10))
        {
            if (from != m && defender != m && SpellHelper.ValidIndirectTarget(from, m)
                && from.CanBeHarmful(m, false) && (!Core.ML || from.InLOS(m)))
            {
                queue.Enqueue(m);
            }
        }

        if (queue.Count == 0)
        {
            return;
        }

        Effects.PlaySound(from.Location, map, sound);

        while (queue.Count > 0)
        {
            var m = queue.Dequeue();

            var scalar = Core.ML ? 1.0 : (11 - from.GetDistanceToSqrt(m)) / 10;

            if (scalar <= 0)
            {
                continue;
            }

            var damage = GetBaseDamage(from);

            if (scalar < 1.0)
            {
                damage *= (11 - from.GetDistanceToSqrt(m)) / 10;
            }

            from.DoHarmful(m, true);
            m.FixedEffect(0x3779, 1, 15, hue, 0);
            AOS.Damage(m, from, (int)damage, phys, fire, cold, pois, nrgy);
        }
    }

    private static bool GetSaveFlag(OldSaveFlag flags, OldSaveFlag toGet) => (flags & toGet) != 0;

    private void Deserialize(IGenericReader reader, int version)
    {
        var flags = (OldSaveFlag)reader.ReadInt();

        if (GetSaveFlag(flags, OldSaveFlag.DamageLevel))
        {
            _damageLevel = (WeaponDamageLevel)reader.ReadInt();
        }

        if (GetSaveFlag(flags, OldSaveFlag.AccuracyLevel))
        {
            _accuracyLevel = (WeaponAccuracyLevel)reader.ReadInt();
        }

        if (GetSaveFlag(flags, OldSaveFlag.DurabilityLevel))
        {
            _durabilityLevel = (WeaponDurabilityLevel)reader.ReadInt();
        }

        if (GetSaveFlag(flags, OldSaveFlag.Quality))
        {
            _quality = (WeaponQuality)reader.ReadInt();
        }
        else
        {
            _quality = WeaponQuality.Regular;
        }

        if (GetSaveFlag(flags, OldSaveFlag.Hits))
        {
            _hitPoints = reader.ReadInt();
        }

        if (GetSaveFlag(flags, OldSaveFlag.MaxHits))
        {
            _maxHitPoints = reader.ReadInt();
        }

        if (GetSaveFlag(flags, OldSaveFlag.Slayer))
        {
            _slayer = (SlayerName)reader.ReadInt();
        }

        if (GetSaveFlag(flags, OldSaveFlag.Poison))
        {
            _poison = reader.ReadPoison();
        }

        if (GetSaveFlag(flags, OldSaveFlag.PoisonCharges))
        {
            _poisonCharges = reader.ReadInt();
        }

        if (GetSaveFlag(flags, OldSaveFlag.Crafter))
        {
            Timer.DelayCall(crafter => _crafter = crafter?.RawName, reader.ReadEntity<Mobile>());
        }

        if (GetSaveFlag(flags, OldSaveFlag.Identified))
        {
            _identified = version >= 6 || reader.ReadBool();
        }

        if (GetSaveFlag(flags, OldSaveFlag.StrReq))
        {
            _strRequirement = reader.ReadInt();
        }
        else
        {
            _strRequirement = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.DexReq))
        {
            _dexRequirement = reader.ReadInt();
        }
        else
        {
            _dexRequirement = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.IntReq))
        {
            _intRequirement = reader.ReadInt();
        }
        else
        {
            _intRequirement = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.MinDamage))
        {
            _minDamage = reader.ReadInt();
        }
        else
        {
            _minDamage = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.MaxDamage))
        {
            _maxDamage = reader.ReadInt();
        }
        else
        {
            _maxDamage = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.HitSound))
        {
            _hitSound = reader.ReadInt();
        }
        else
        {
            _hitSound = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.MissSound))
        {
            _missSound = reader.ReadInt();
        }
        else
        {
            _missSound = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.Speed))
        {
            if (version < 9)
            {
                _speed = reader.ReadInt();
            }
            else
            {
                _speed = reader.ReadFloat();
            }
        }
        else
        {
            _speed = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.MaxRange))
        {
            _maxRange = reader.ReadInt();
        }
        else
        {
            _maxRange = -1;
        }

        if (GetSaveFlag(flags, OldSaveFlag.Skill))
        {
            _skill = (SkillName)reader.ReadInt();
        }
        else
        {
            _skill = (SkillName)(-1);
        }

        if (GetSaveFlag(flags, OldSaveFlag.Type))
        {
            _type = (WeaponType)reader.ReadInt();
        }
        else
        {
            _type = (WeaponType)(-1);
        }

        if (GetSaveFlag(flags, OldSaveFlag.Animation))
        {
            _animation = (WeaponAnimation)reader.ReadInt();
        }
        else
        {
            _animation = (WeaponAnimation)(-1);
        }

        if (GetSaveFlag(flags, OldSaveFlag.Resource))
        {
            _resource = (CraftResource)reader.ReadInt();
        }
        else
        {
            _resource = CraftResource.Iron;
        }

        Attributes = new AosAttributes(this);

        if (GetSaveFlag(flags, OldSaveFlag.Attributes))
        {
            Attributes.Deserialize(reader);
        }

        WeaponAttributes = new AosWeaponAttributes(this);

        if (GetSaveFlag(flags, OldSaveFlag.WeaponAttributes))
        {
            WeaponAttributes.Deserialize(reader);
        }

        PlayerConstructed = GetSaveFlag(flags, OldSaveFlag.PlayerConstructed);

        SkillBonuses = new AosSkillBonuses(this);

        if (GetSaveFlag(flags, OldSaveFlag.SkillBonuses))
        {
            SkillBonuses.Deserialize(reader);
        }

        if (GetSaveFlag(flags, OldSaveFlag.Slayer2))
        {
            _slayer2 = (SlayerName)reader.ReadInt();
        }

        AosElementDamages = new AosElementAttributes(this);

        if (GetSaveFlag(flags, OldSaveFlag.ElementalDamages))
        {
            AosElementDamages.Deserialize(reader);
        }

        if (GetSaveFlag(flags, OldSaveFlag.EngravedText))
        {
            _engravedText = reader.ReadString();
        }
    }

    [AfterDeserialization]
    private void AfterDeserialization()
    {
        var parentMobile = Parent as Mobile;

        if (UseSkillMod && _accuracyLevel != WeaponAccuracyLevel.Regular && parentMobile != null)
        {
            m_SkillMod = new DefaultSkillMod(AccuracySkill, "WeaponAccuracy", true, (int)_accuracyLevel * 5);
            parentMobile.AddSkillMod(m_SkillMod);
        }

        if (Core.AOS && WeaponAttributes.MageWeapon != 0 && WeaponAttributes.MageWeapon != 30 &&
            parentMobile != null)
        {
            m_MageMod = new DefaultSkillMod(SkillName.Magery, "MageWeapon", true, -30 + WeaponAttributes.MageWeapon);
            parentMobile.AddSkillMod(m_MageMod);
        }

        if (Core.AOS && parentMobile != null)
        {
            SkillBonuses.AddTo(parentMobile);
        }

        var strBonus = Attributes.BonusStr;
        var dexBonus = Attributes.BonusDex;
        var intBonus = Attributes.BonusInt;

        if (parentMobile != null && (strBonus != 0 || dexBonus != 0 || intBonus != 0))
        {
            if (strBonus != 0)
            {
                parentMobile.AddStatMod(new StatMod(StatType.Str, $"{Serial}Str", strBonus, TimeSpan.Zero));
            }

            if (dexBonus != 0)
            {
                parentMobile.AddStatMod(new StatMod(StatType.Dex, $"{Serial}Dex", dexBonus, TimeSpan.Zero));
            }

            if (intBonus != 0)
            {
                parentMobile.AddStatMod(new StatMod(StatType.Int, $"{Serial}Int", intBonus, TimeSpan.Zero));
            }
        }

        parentMobile?.CheckStatTimers();

        if (_hitPoints <= 0 && _maxHitPoints <= 0)
        {
            _hitPoints = _maxHitPoints = Utility.RandomMinMax(InitMinHits, InitMaxHits);
        }
    }

    private class ResetEquipTimer : Timer
    {
        private readonly Mobile m_Mobile;

        public ResetEquipTimer(Mobile m, TimeSpan duration) : base(duration) => m_Mobile = m;

        protected override void OnTick()
        {
            m_Mobile.EndAction<BaseWeapon>();
        }
    }

    [Flags]
    private enum OldSaveFlag
    {
        None = 0x00000000,
        DamageLevel = 0x00000001,
        AccuracyLevel = 0x00000002,
        DurabilityLevel = 0x00000004,
        Quality = 0x00000008,
        Hits = 0x00000010,
        MaxHits = 0x00000020,
        Slayer = 0x00000040,
        Poison = 0x00000080,
        PoisonCharges = 0x00000100,
        Crafter = 0x00000200,
        Identified = 0x00000400,
        StrReq = 0x00000800,
        DexReq = 0x00001000,
        IntReq = 0x00002000,
        MinDamage = 0x00004000,
        MaxDamage = 0x00008000,
        HitSound = 0x00010000,
        MissSound = 0x00020000,
        Speed = 0x00040000,
        MaxRange = 0x00080000,
        Skill = 0x00100000,
        Type = 0x00200000,
        Animation = 0x00400000,
        Resource = 0x00800000,
        Attributes = 0x01000000,
        WeaponAttributes = 0x02000000,
        PlayerConstructed = 0x04000000,
        SkillBonuses = 0x08000000,
        Slayer2 = 0x10000000,
        ElementalDamages = 0x20000000,
        EngravedText = 0x40000000
    }
}

public enum CheckSlayerResult
{
    None,
    Slayer,
    Opposition
}
