ModernUO/Projects/UOContent/Items/Weapons/BaseWeapon.cs
Jack 6cdec42146
fix: Special moves and various UOR+ bonuses (#2384)
## Special moves
Special moves were added [27 Apr, 2000.](https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2000-2/2000-publish-05-27th-april/). This is well past the discussed cutoff date for T2A, which is **[Nov 23, 1999 - Publish 1](https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/1999-2/1999-publish-01-23rd-november/)** as per the UOSecondAge shard which is the golden standard for T2A accuracy and represents a logical cutoff for this era. 

Regarding the specific date: **Nov 23, 1999 (Publish 1)** - the game significantly changed as a "pre-patch" to UOR on this date, and UOSecondAge picks and chooses from this date based on QoL features, while keeping the gameplay accurate to what is widely known as the "T2A era". However this date is ~4 months before the actual UOR release date.

## Defensive wrestling
Defensively wrestling as (eval+anat)/2 was added in [publish 16](https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2002-2/publish-16-changes-and-bug-fixes-24th-july/) and should be gated behind !Core.LBR
* "Unarmed “to-be-hit” changes (chance to get hit while unarmed is now the better of either Wrestling skill, or an average of Anatomy & Evaluate Intelligence)."

## Lumberjacking damage bonus

Lumberjacking damage bonus was added in [UOR](https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2000-2/2000-publish-05-27th-april/)

## Anatomy and LJ +10% GM bonus
Anatomy/LJ +10% GM bonus was added in [pub 13](https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2001-2/2001-publish-13-19th-august/), which was during UOTD.
2026-03-22 10:55:01 -07:00

4100 lines
118 KiB
C#

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 (Core.UOR && 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 (UOTD+)
*/
var anatomyValue = attacker.Skills.Anatomy.Value;
modifiers += anatomyValue / 5.0 / 100.0;
if (Core.UOTD && anatomyValue >= 100.0)
{
modifiers += 0.1;
}
/* Compute lumberjacking bonus (UOR+)
* : 1% bonus for every 5 points of lumberjacking
* : +10% bonus at Grandmaster or higher (UOTD+)
*/
if (Core.UOR && Type == WeaponType.Axe)
{
var lumberValue = attacker.Skills.Lumberjacking.Value;
modifiers += lumberValue / 5.0 / 100.0;
if (Core.UOTD && 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
}