- [X] Changes outgoing combat packets - [X] Moves quest arrow to PlayerMobile and moves quest arrow packets to content. Bumps release version
4346 lines
138 KiB
C#
4346 lines
138 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Server.Engines.Craft;
|
|
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;
|
|
|
|
namespace Server.Items
|
|
{
|
|
public interface ISlayer
|
|
{
|
|
SlayerName Slayer { get; set; }
|
|
SlayerName Slayer2 { get; set; }
|
|
}
|
|
|
|
public abstract class BaseWeapon : Item, IWeapon, IFactionItem, ICraftable, ISlayer, IDurability
|
|
{
|
|
private WeaponAccuracyLevel m_AccuracyLevel;
|
|
private WeaponAnimation m_Animation;
|
|
private Mobile m_Crafter;
|
|
|
|
/* 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
|
|
*/
|
|
|
|
// Instance values. These values are unique to each weapon.
|
|
private WeaponDamageLevel m_DamageLevel;
|
|
private WeaponDurabilityLevel m_DurabilityLevel;
|
|
private string m_EngravedText;
|
|
|
|
private FactionItem m_FactionState;
|
|
private int m_Hits;
|
|
private int m_HitSound, m_MissSound;
|
|
private bool m_Identified;
|
|
private int m_MaxHits;
|
|
private int m_MaxRange;
|
|
private int m_MinDamage, m_MaxDamage;
|
|
private Poison m_Poison;
|
|
private int m_PoisonCharges;
|
|
private WeaponQuality m_Quality;
|
|
private CraftResource m_Resource;
|
|
private SkillName m_Skill;
|
|
private SkillMod m_SkillMod, m_MageMod;
|
|
private SlayerName m_Slayer;
|
|
private SlayerName m_Slayer2;
|
|
private float m_Speed;
|
|
|
|
// Overridable values. These values are provided to override the defaults which get defined in the individual weapon scripts.
|
|
private int m_StrReq, m_DexReq, m_IntReq;
|
|
private WeaponType m_Type;
|
|
|
|
public BaseWeapon(int itemID) : base(itemID)
|
|
{
|
|
Layer = (Layer)ItemData.Quality;
|
|
|
|
m_Quality = WeaponQuality.Regular;
|
|
m_StrReq = -1;
|
|
m_DexReq = -1;
|
|
m_IntReq = -1;
|
|
m_MinDamage = -1;
|
|
m_MaxDamage = -1;
|
|
m_HitSound = -1;
|
|
m_MissSound = -1;
|
|
m_Speed = -1;
|
|
m_MaxRange = -1;
|
|
m_Skill = (SkillName)(-1);
|
|
m_Type = (WeaponType)(-1);
|
|
m_Animation = (WeaponAnimation)(-1);
|
|
|
|
m_Hits = m_MaxHits = Utility.RandomMinMax(InitMinHits, InitMaxHits);
|
|
|
|
m_Resource = CraftResource.Iron;
|
|
|
|
Attributes = new AosAttributes(this);
|
|
WeaponAttributes = new AosWeaponAttributes(this);
|
|
SkillBonuses = new AosSkillBonuses(this);
|
|
AosElementDamages = new AosElementAttributes(this);
|
|
}
|
|
|
|
public BaseWeapon(Serial serial) : base(serial)
|
|
{
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string EngravedText
|
|
{
|
|
get => m_EngravedText;
|
|
set
|
|
{
|
|
m_EngravedText = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
public virtual Race RequiredRace =>
|
|
null; // On OSI, there are no weapons with race requirements, this is for custom stuff
|
|
|
|
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 static BaseWeapon Fists { get; set; }
|
|
|
|
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 AosAttributes Attributes { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public AosWeaponAttributes WeaponAttributes { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public AosSkillBonuses SkillBonuses { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public AosElementAttributes AosElementDamages { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Cursed { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Consecrated { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Identified
|
|
{
|
|
get => m_Identified;
|
|
set
|
|
{
|
|
m_Identified = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int PoisonCharges
|
|
{
|
|
get => m_PoisonCharges;
|
|
set
|
|
{
|
|
m_PoisonCharges = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Poison Poison
|
|
{
|
|
get => m_Poison;
|
|
set
|
|
{
|
|
m_Poison = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public WeaponQuality Quality
|
|
{
|
|
get => m_Quality;
|
|
set
|
|
{
|
|
UnscaleDurability();
|
|
m_Quality = value;
|
|
ScaleDurability();
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Mobile Crafter
|
|
{
|
|
get => m_Crafter;
|
|
set
|
|
{
|
|
m_Crafter = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public CraftResource Resource
|
|
{
|
|
get => m_Resource;
|
|
set
|
|
{
|
|
UnscaleDurability();
|
|
m_Resource = value;
|
|
Hue = CraftResources.GetHue(m_Resource);
|
|
InvalidateProperties();
|
|
ScaleDurability();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public WeaponDamageLevel DamageLevel
|
|
{
|
|
get => m_DamageLevel;
|
|
set
|
|
{
|
|
m_DamageLevel = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public WeaponDurabilityLevel DurabilityLevel
|
|
{
|
|
get => m_DurabilityLevel;
|
|
set
|
|
{
|
|
UnscaleDurability();
|
|
m_DurabilityLevel = value;
|
|
InvalidateProperties();
|
|
ScaleDurability();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool PlayerConstructed { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public WeaponAnimation Animation
|
|
{
|
|
get => m_Animation == (WeaponAnimation)(-1) ? Core.AOS ? AosAnimation : OldAnimation : m_Animation;
|
|
set => m_Animation = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public WeaponType Type
|
|
{
|
|
get => m_Type == (WeaponType)(-1) ? Core.AOS ? AosType : OldType : m_Type;
|
|
set => m_Type = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SkillName Skill
|
|
{
|
|
get => m_Skill == (SkillName)(-1) ? Core.AOS ? AosSkill : OldSkill : m_Skill;
|
|
set
|
|
{
|
|
m_Skill = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitSound
|
|
{
|
|
get => m_HitSound == -1 ? Core.AOS ? AosHitSound : OldHitSound : m_HitSound;
|
|
set => m_HitSound = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MissSound
|
|
{
|
|
get => m_MissSound == -1 ? Core.AOS ? AosMissSound : OldMissSound : m_MissSound;
|
|
set => m_MissSound = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MinDamage
|
|
{
|
|
get => m_MinDamage == -1 ? Core.AOS ? AosMinDamage : OldMinDamage : m_MinDamage;
|
|
set
|
|
{
|
|
m_MinDamage = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MaxDamage
|
|
{
|
|
get => m_MaxDamage == -1 ? Core.AOS ? AosMaxDamage : OldMaxDamage : m_MaxDamage;
|
|
set
|
|
{
|
|
m_MaxDamage = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public float Speed
|
|
{
|
|
get
|
|
{
|
|
if (m_Speed != -1)
|
|
{
|
|
return m_Speed;
|
|
}
|
|
|
|
if (Core.ML)
|
|
{
|
|
return MlSpeed;
|
|
}
|
|
|
|
if (Core.AOS)
|
|
{
|
|
return AosSpeed;
|
|
}
|
|
|
|
return OldSpeed;
|
|
}
|
|
set
|
|
{
|
|
m_Speed = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int StrRequirement
|
|
{
|
|
get => m_StrReq == -1 ? Core.AOS ? AosStrengthReq : OldStrengthReq : m_StrReq;
|
|
set
|
|
{
|
|
m_StrReq = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int DexRequirement
|
|
{
|
|
get => m_DexReq == -1 ? Core.AOS ? AosDexterityReq : OldDexterityReq : m_DexReq;
|
|
set => m_DexReq = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int IntRequirement
|
|
{
|
|
get => m_IntReq == -1 ? Core.AOS ? AosIntelligenceReq : OldIntelligenceReq : m_IntReq;
|
|
set => m_IntReq = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public WeaponAccuracyLevel AccuracyLevel
|
|
{
|
|
get => m_AccuracyLevel;
|
|
set
|
|
{
|
|
if (m_AccuracyLevel != value)
|
|
{
|
|
m_AccuracyLevel = value;
|
|
|
|
if (UseSkillMod)
|
|
{
|
|
if (m_AccuracyLevel == WeaponAccuracyLevel.Regular)
|
|
{
|
|
m_SkillMod?.Remove();
|
|
|
|
m_SkillMod = null;
|
|
}
|
|
else if (m_SkillMod == null && Parent is Mobile mobile)
|
|
{
|
|
m_SkillMod = new DefaultSkillMod(AccuracySkill, true, (int)m_AccuracyLevel * 5);
|
|
mobile.AddSkillMod(m_SkillMod);
|
|
}
|
|
else if (m_SkillMod != null)
|
|
{
|
|
m_SkillMod.Value = (int)m_AccuracyLevel * 5;
|
|
}
|
|
}
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
}
|
|
|
|
public 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;
|
|
}
|
|
|
|
PlayerConstructed = 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)
|
|
{
|
|
if (Attributes.WeaponDamage > 35)
|
|
{
|
|
Attributes.WeaponDamage -= 20;
|
|
}
|
|
else
|
|
{
|
|
Attributes.WeaponDamage = 15;
|
|
}
|
|
|
|
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:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Durable;
|
|
AccuracyLevel = WeaponAccuracyLevel.Accurate;
|
|
break;
|
|
}
|
|
case CraftResource.ShadowIron:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Durable;
|
|
DamageLevel = WeaponDamageLevel.Ruin;
|
|
break;
|
|
}
|
|
case CraftResource.Copper:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Fortified;
|
|
DamageLevel = WeaponDamageLevel.Ruin;
|
|
AccuracyLevel = WeaponAccuracyLevel.Surpassingly;
|
|
break;
|
|
}
|
|
case CraftResource.Bronze:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Fortified;
|
|
DamageLevel = WeaponDamageLevel.Might;
|
|
AccuracyLevel = WeaponAccuracyLevel.Surpassingly;
|
|
break;
|
|
}
|
|
case CraftResource.Gold:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Indestructible;
|
|
DamageLevel = WeaponDamageLevel.Force;
|
|
AccuracyLevel = WeaponAccuracyLevel.Eminently;
|
|
break;
|
|
}
|
|
case CraftResource.Agapite:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Indestructible;
|
|
DamageLevel = WeaponDamageLevel.Power;
|
|
AccuracyLevel = WeaponAccuracyLevel.Eminently;
|
|
break;
|
|
}
|
|
case CraftResource.Verite:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Indestructible;
|
|
DamageLevel = WeaponDamageLevel.Power;
|
|
AccuracyLevel = WeaponAccuracyLevel.Exceedingly;
|
|
break;
|
|
}
|
|
case CraftResource.Valorite:
|
|
{
|
|
Identified = true;
|
|
DurabilityLevel = WeaponDurabilityLevel.Indestructible;
|
|
DamageLevel = WeaponDamageLevel.Vanq;
|
|
AccuracyLevel = WeaponAccuracyLevel.Supremely;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return quality;
|
|
}
|
|
|
|
public virtual void UnscaleDurability()
|
|
{
|
|
var scale = 100 + GetDurabilityBonus();
|
|
|
|
m_Hits = (m_Hits * 100 + (scale - 1)) / scale;
|
|
m_MaxHits = (m_MaxHits * 100 + (scale - 1)) / scale;
|
|
InvalidateProperties();
|
|
}
|
|
|
|
public virtual void ScaleDurability()
|
|
{
|
|
var scale = 100 + GetDurabilityBonus();
|
|
|
|
m_Hits = (m_Hits * scale + 99) / 100;
|
|
m_MaxHits = (m_MaxHits * scale + 99) / 100;
|
|
InvalidateProperties();
|
|
}
|
|
|
|
public virtual int InitMinHits => 0;
|
|
public virtual int InitMaxHits => 0;
|
|
|
|
public virtual bool CanFortify => true;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitPoints
|
|
{
|
|
get => m_Hits;
|
|
set
|
|
{
|
|
if (m_Hits == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (value > m_MaxHits)
|
|
{
|
|
value = m_MaxHits;
|
|
}
|
|
|
|
m_Hits = value;
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MaxHitPoints
|
|
{
|
|
get => m_MaxHits;
|
|
set
|
|
{
|
|
m_MaxHits = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SlayerName Slayer
|
|
{
|
|
get => m_Slayer;
|
|
set
|
|
{
|
|
m_Slayer = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SlayerName Slayer2
|
|
{
|
|
get => m_Slayer2;
|
|
set
|
|
{
|
|
m_Slayer2 = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
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 TimeSpan OnSwing(Mobile attacker, Mobile defender) => OnSwing(attacker, defender, 1.0);
|
|
|
|
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)
|
|
{
|
|
var canSwing = true;
|
|
|
|
if (Core.AOS)
|
|
{
|
|
canSwing = !attacker.Paralyzed && !attacker.Frozen;
|
|
|
|
if (canSwing)
|
|
{
|
|
canSwing = !(attacker.Spell is Spell sp) || !sp.IsCasting || !sp.BlocksMovement;
|
|
}
|
|
|
|
if (canSwing)
|
|
{
|
|
canSwing = !(attacker is PlayerMobile p) || p.PeacedUntil <= DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MaxRange
|
|
{
|
|
get => m_MaxRange == -1 ? Core.AOS ? AosMaxRange : OldMaxRange : m_MaxRange;
|
|
set
|
|
{
|
|
m_MaxRange = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
public override void OnAfterDuped(Item newItem)
|
|
{
|
|
if (!(newItem is 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);
|
|
}
|
|
|
|
public int GetDurabilityBonus()
|
|
{
|
|
var bonus = 0;
|
|
|
|
if (m_Quality == WeaponQuality.Exceptional)
|
|
{
|
|
bonus += 20;
|
|
}
|
|
|
|
switch (m_DurabilityLevel)
|
|
{
|
|
case WeaponDurabilityLevel.Durable:
|
|
bonus += 20;
|
|
break;
|
|
case WeaponDurabilityLevel.Substantial:
|
|
bonus += 50;
|
|
break;
|
|
case WeaponDurabilityLevel.Massive:
|
|
bonus += 70;
|
|
break;
|
|
case WeaponDurabilityLevel.Fortified:
|
|
bonus += 100;
|
|
break;
|
|
case WeaponDurabilityLevel.Indestructible:
|
|
bonus += 120;
|
|
break;
|
|
}
|
|
|
|
if (Core.AOS)
|
|
{
|
|
bonus += WeaponAttributes.DurabilityBonus;
|
|
|
|
var resInfo = CraftResources.GetInfo(m_Resource);
|
|
CraftAttributeInfo attrInfo = null;
|
|
|
|
if (resInfo != null)
|
|
{
|
|
attrInfo = resInfo.AttributeInfo;
|
|
}
|
|
|
|
if (attrInfo != null)
|
|
{
|
|
bonus += attrInfo.WeaponDurability;
|
|
}
|
|
}
|
|
|
|
return bonus;
|
|
}
|
|
|
|
public int GetLowerStatReq()
|
|
{
|
|
if (!Core.AOS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var v = WeaponAttributes.LowerStatReq;
|
|
|
|
var attrInfo = CraftResources.GetInfo(m_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 BaseShield) &&
|
|
!(item is 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 (!Ethic.CheckEquip(from, this))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (RequiredRace != null && from.Race != RequiredRace)
|
|
{
|
|
if (RequiredRace == Race.Elf)
|
|
{
|
|
from.SendLocalizedMessage(1072203); // Only Elves may use this.
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage("Only {0} may use this.", RequiredRace.PluralName);
|
|
}
|
|
|
|
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;
|
|
|
|
var modName = Serial.ToString();
|
|
|
|
if (strBonus != 0)
|
|
{
|
|
m.AddStatMod(new StatMod(StatType.Str, $"{modName}Str", strBonus, TimeSpan.Zero));
|
|
}
|
|
|
|
if (dexBonus != 0)
|
|
{
|
|
m.AddStatMod(new StatMod(StatType.Dex, $"{modName}Dex", dexBonus, TimeSpan.Zero));
|
|
}
|
|
|
|
if (intBonus != 0)
|
|
{
|
|
m.AddStatMod(new StatMod(StatType.Int, $"{modName}Int", intBonus, TimeSpan.Zero));
|
|
}
|
|
}
|
|
|
|
from.NextCombatTime = Core.TickCount + (int)GetDelay(from).TotalMilliseconds;
|
|
|
|
if (UseSkillMod && m_AccuracyLevel != WeaponAccuracyLevel.Regular)
|
|
{
|
|
m_SkillMod?.Remove();
|
|
|
|
m_SkillMod = new DefaultSkillMod(AccuracySkill, true, (int)m_AccuracyLevel * 5);
|
|
from.AddSkillMod(m_SkillMod);
|
|
}
|
|
|
|
if (Core.AOS && WeaponAttributes.MageWeapon != 0 && WeaponAttributes.MageWeapon != 30)
|
|
{
|
|
m_MageMod?.Remove();
|
|
|
|
m_MageMod = new DefaultSkillMod(SkillName.Magery, 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 Mobile m)
|
|
{
|
|
var weapon = m.Weapon as BaseWeapon;
|
|
|
|
var modName = Serial.ToString();
|
|
|
|
m.RemoveStatMod($"{modName}Str");
|
|
m.RemoveStatMod($"{modName}Dex");
|
|
m.RemoveStatMod($"{modName}Int");
|
|
|
|
if (weapon != null)
|
|
{
|
|
m.NextCombatTime = Core.TickCount + (int)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);
|
|
|
|
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;
|
|
|
|
private static bool CheckAnimal(Mobile m, Type type) => AnimalForm.UnderTransformation(m, type);
|
|
|
|
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];
|
|
// Skill defSkill = defender.Skills[defWeapon.Skill];
|
|
|
|
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 (CheckAnimal(attacker, typeof(GreyWolf)) || CheckAnimal(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);
|
|
|
|
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
|
|
{
|
|
if (atkValue <= -50.0)
|
|
{
|
|
atkValue = -49.9;
|
|
}
|
|
|
|
if (defValue <= -50.0)
|
|
{
|
|
defValue = -49.9;
|
|
}
|
|
|
|
ourValue = atkValue + 50.0;
|
|
theirValue = defValue + 50.0;
|
|
}
|
|
|
|
var chance = ourValue / (theirValue * 2.0);
|
|
|
|
chance *= 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);
|
|
|
|
if (DivineFurySpell.UnderEffect(m))
|
|
{
|
|
bonus += 10;
|
|
}
|
|
|
|
// Bonus granted by successful use of Honorable Execution.
|
|
bonus += HonorableExecution.GetSwingBonus(m);
|
|
|
|
if (DualWield.Registry.TryGetValue(m, out var duelWield))
|
|
{
|
|
bonus += duelWield.BonusSwingSpeed;
|
|
}
|
|
|
|
if (Feint.Registry.TryGetValue(m, out var feint))
|
|
{
|
|
bonus -= feint.SwingSpeedReduction;
|
|
}
|
|
|
|
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(Layer.TwoHanded) as BaseShield;
|
|
|
|
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 || defender.Weapon is 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);
|
|
}
|
|
|
|
return
|
|
aosChance > Utility
|
|
.RandomDouble(); // Only skillcheck if wielding a shield & there's no effect from Bushido
|
|
}
|
|
|
|
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))
|
|
{
|
|
defender.SendLocalizedMessage(
|
|
1063117
|
|
); // Your confidence reassures you as you successfully block your opponent's blow.
|
|
|
|
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(Layer.TwoHanded) as BaseShield;
|
|
|
|
shield?.OnHit(this, damage);
|
|
}
|
|
}
|
|
|
|
if (!blocked)
|
|
{
|
|
var positionChance = Utility.RandomDouble();
|
|
|
|
Item armorItem;
|
|
|
|
if (positionChance < 0.07)
|
|
{
|
|
armorItem = defender.NeckArmor;
|
|
}
|
|
else if (positionChance < 0.14)
|
|
{
|
|
armorItem = defender.HandArmor;
|
|
}
|
|
else if (positionChance < 0.28)
|
|
{
|
|
armorItem = defender.ArmsArmor;
|
|
}
|
|
else if (positionChance < 0.43)
|
|
{
|
|
armorItem = defender.HeadArmor;
|
|
}
|
|
else if (positionChance < 0.65)
|
|
{
|
|
armorItem = defender.LegsArmor;
|
|
}
|
|
else
|
|
{
|
|
armorItem = 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();
|
|
|
|
Item armorItem;
|
|
|
|
if (chance < 0.07)
|
|
{
|
|
armorItem = defender.NeckArmor;
|
|
}
|
|
else if (chance < 0.14)
|
|
{
|
|
armorItem = defender.HandArmor;
|
|
}
|
|
else if (chance < 0.28)
|
|
{
|
|
armorItem = defender.ArmsArmor;
|
|
}
|
|
else if (chance < 0.43)
|
|
{
|
|
armorItem = defender.HeadArmor;
|
|
}
|
|
else if (chance < 0.65)
|
|
{
|
|
armorItem = defender.LegsArmor;
|
|
}
|
|
else
|
|
{
|
|
armorItem = defender.ChestArmor;
|
|
}
|
|
|
|
if (armorItem is IWearableDurability armor)
|
|
{
|
|
damage = armor.OnHit(this, damage);
|
|
}
|
|
|
|
var virtualArmor = defender.VirtualArmor + defender.VirtualArmorMod;
|
|
|
|
if (virtualArmor > 0)
|
|
{
|
|
double scalar;
|
|
|
|
if (chance < 0.14)
|
|
{
|
|
scalar = 0.07;
|
|
}
|
|
else if (chance < 0.28)
|
|
{
|
|
scalar = 0.14;
|
|
}
|
|
else if (chance < 0.43)
|
|
{
|
|
scalar = 0.15;
|
|
}
|
|
else if (chance < 0.65)
|
|
{
|
|
scalar = 0.22;
|
|
}
|
|
else
|
|
{
|
|
scalar = 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 BaseCreature bc) || bc.PackInstinct == PackInstinct.None || !bc.Controlled && !bc.Summoned)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var master = bc.ControlMaster ?? bc.SummonMaster;
|
|
|
|
if (master == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var eable = defender.GetMobilesInRange<BaseCreature>(1);
|
|
var inPack = 1 + eable
|
|
.Where(m => m != attacker && (m.PackInstinct & bc.PackInstinct) != 0 && (m.Controlled || m.Summoned))
|
|
.Count(m => master == (m.ControlMaster ?? m.SummonMaster) && m.Combatant == defender);
|
|
|
|
eable.Free();
|
|
|
|
return inPack >= 5 ? 100 :
|
|
inPack >= 4 ? 75 :
|
|
inPack >= 3 ? 50 :
|
|
inPack >= 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())
|
|
{
|
|
var eable = defender.GetMobilesInRange<Clone>(4);
|
|
foreach (var m in eable)
|
|
{
|
|
if (m?.Summoned == true && m.SummonMaster == defender)
|
|
{
|
|
attacker.SendLocalizedMessage(
|
|
1063141
|
|
); // Your attack has been diverted to a nearby mirror image of your target!
|
|
defender.SendLocalizedMessage(
|
|
1063140
|
|
); // You manage to divert the attack onto one of your nearby mirror images.
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
eable.Free();
|
|
}
|
|
|
|
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)(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)
|
|
{
|
|
if (pm.EnemyOfOneType != null && pm.EnemyOfOneType != attacker.GetType())
|
|
{
|
|
percentageBonus += 100;
|
|
}
|
|
}
|
|
}
|
|
else if (!defender.Player)
|
|
{
|
|
if (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 ((m_Slayer == SlayerName.Silver || m_Slayer2 == SlayerName.Silver) && context?.Spell is NecromancerSpell &&
|
|
context.Type != typeof(HorrificBeastSpell))
|
|
{
|
|
percentageBonus += 25;
|
|
}
|
|
|
|
if (attacker is PlayerMobile pmAttacker && !(Core.ML && defender is PlayerMobile))
|
|
{
|
|
if (pmAttacker.HonorActive && 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 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;
|
|
}
|
|
else if (Core.AOS && damage == 0) // parried
|
|
{
|
|
if (a?.Validate(attacker) == true) /*&& a.CheckMana( attacker, true )*/
|
|
// Parried special moves have no mana cost
|
|
{
|
|
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)
|
|
{
|
|
if (attacker.FindItemOnLayer(Layer.Cloak) is BaseQuiver quiver)
|
|
{
|
|
quiver.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 if (type == 4)
|
|
{
|
|
nrgy = 100;
|
|
}
|
|
}
|
|
|
|
// TODO: Scale damage, alongside the leech effects below, to weapon speed.
|
|
if (ImmolatingWeaponSpell.IsImmolating(this) && damage > 0)
|
|
{
|
|
ImmolatingWeaponSpell.DoEffect(this, defender);
|
|
}
|
|
|
|
var damageGiven = damage;
|
|
|
|
if (a?.OnBeforeDamage(attacker, defender) == false)
|
|
{
|
|
WeaponAbility.ClearCurrentAbility(attacker);
|
|
a = null;
|
|
}
|
|
|
|
if (move?.OnBeforeDamage(attacker, defender) == false)
|
|
{
|
|
SpecialMove.ClearCurrentMove(attacker);
|
|
move = null;
|
|
}
|
|
|
|
var ignoreArmor = a is ArmorIgnore || move?.IgnoreArmor(attacker) == true;
|
|
|
|
damageGiven = AOS.Damage(
|
|
defender,
|
|
attacker,
|
|
damage,
|
|
ignoreArmor,
|
|
phys,
|
|
fire,
|
|
cold,
|
|
pois,
|
|
nrgy,
|
|
chaos,
|
|
direct,
|
|
false,
|
|
this is BaseRanged
|
|
);
|
|
|
|
var propertyBonus = move?.GetPropertyBonus(attacker) ?? 1.0;
|
|
|
|
if (Core.AOS)
|
|
{
|
|
var lifeLeech = 0;
|
|
var stamLeech = 0;
|
|
var manaLeech = 0;
|
|
int wraithLeech;
|
|
|
|
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)
|
|
}
|
|
|
|
context = TransformationSpellHelper.GetContext(attacker);
|
|
|
|
if (context?.Type == typeof(VampiricEmbraceSpell))
|
|
{
|
|
lifeLeech += 20; // Vampiric embrace gives an additional 20% life leech
|
|
}
|
|
|
|
if (context?.Type == typeof(WraithFormSpell))
|
|
{
|
|
wraithLeech =
|
|
5 + (int)(15 * attacker.Skills.SpiritSpeak.Value /
|
|
100); // Wraith form gives an additional 5-20% mana leech
|
|
|
|
// Mana leeched by the Wraith Form spell is actually stolen, not just leeched.
|
|
defender.Mana -= AOS.Scale(damageGiven, wraithLeech);
|
|
|
|
manaLeech += wraithLeech;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (m_MaxHits > 0 && (MaxRange <= 1 && (defender is Slime || defender is AcidElemental) ||
|
|
Utility.RandomDouble() < .04)) // Stratics says 50% chance, seems more like 4%..
|
|
{
|
|
if (MaxRange <= 1 && (defender is Slime || defender is AcidElemental))
|
|
{
|
|
attacker.LocalOverheadMessage(MessageType.Regular, 0x3B2, 500263); // *Acid blood scars your weapon!*
|
|
}
|
|
|
|
if (Core.AOS && WeaponAttributes.SelfRepair > Utility.Random(10))
|
|
{
|
|
HitPoints += 2;
|
|
}
|
|
else
|
|
{
|
|
if (m_Hits > 0)
|
|
{
|
|
--HitPoints;
|
|
}
|
|
else if (m_MaxHits > 1)
|
|
{
|
|
--MaxHitPoints;
|
|
|
|
if (Parent is Mobile mobile)
|
|
{
|
|
mobile.LocalOverheadMessage(
|
|
MessageType.Regular,
|
|
0x3B2,
|
|
1061121
|
|
); // Your equipment is severely damaged.
|
|
}
|
|
}
|
|
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);
|
|
bcDef?.OnGotMeleeAttack(attacker);
|
|
|
|
a?.OnHit(attacker, defender, damage);
|
|
move?.OnHit(attacker, defender, damage);
|
|
|
|
if (defender is IHonorTarget it)
|
|
{
|
|
it.ReceivedHonorContext?.OnTargetHit(attacker);
|
|
}
|
|
|
|
if (!(this is 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, uint dice, uint 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);
|
|
|
|
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)
|
|
{
|
|
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(m_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));
|
|
|
|
var ability = WeaponAbility.GetCurrentAbility(attacker);
|
|
|
|
ability?.OnMiss(attacker, defender);
|
|
|
|
var move = SpecialMove.GetCurrentMove(attacker);
|
|
|
|
move?.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 (m_DamageLevel != WeaponDamageLevel.Regular)
|
|
{
|
|
damage += 2 * (int)m_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;
|
|
}
|
|
|
|
var bonus = 0;
|
|
|
|
switch (m_AccuracyLevel)
|
|
{
|
|
case WeaponAccuracyLevel.Accurate:
|
|
bonus += 02;
|
|
break;
|
|
case WeaponAccuracyLevel.Surpassingly:
|
|
bonus += 04;
|
|
break;
|
|
case WeaponAccuracyLevel.Eminently:
|
|
bonus += 06;
|
|
break;
|
|
case WeaponAccuracyLevel.Exceedingly:
|
|
bonus += 08;
|
|
break;
|
|
case WeaponAccuracyLevel.Supremely:
|
|
bonus += 10;
|
|
break;
|
|
}
|
|
|
|
return bonus;
|
|
}
|
|
|
|
public virtual int GetDamageBonus()
|
|
{
|
|
var bonus = VirtualDamageBonus;
|
|
|
|
switch (m_Quality)
|
|
{
|
|
case WeaponQuality.Low:
|
|
bonus -= 20;
|
|
break;
|
|
case WeaponQuality.Exceptional:
|
|
bonus += 20;
|
|
break;
|
|
}
|
|
|
|
switch (m_DamageLevel)
|
|
{
|
|
case WeaponDamageLevel.Ruin:
|
|
bonus += 15;
|
|
break;
|
|
case WeaponDamageLevel.Might:
|
|
bonus += 20;
|
|
break;
|
|
case WeaponDamageLevel.Force:
|
|
bonus += 25;
|
|
break;
|
|
case WeaponDamageLevel.Power:
|
|
bonus += 30;
|
|
break;
|
|
case WeaponDamageLevel.Vanq:
|
|
bonus += 35;
|
|
break;
|
|
}
|
|
|
|
return bonus;
|
|
}
|
|
|
|
public virtual double ScaleDamageAOS(Mobile attacker, double damage, bool checkSkills)
|
|
{
|
|
if (checkSkills)
|
|
{
|
|
attacker.CheckSkill(
|
|
SkillName.Tactics,
|
|
0.0,
|
|
attacker.Skills.Tactics.Cap
|
|
); // Passively check tactics for gain
|
|
attacker.CheckSkill(
|
|
SkillName.Anatomy,
|
|
0.0,
|
|
attacker.Skills.Anatomy.Cap
|
|
); // Passively check Anatomy for gain
|
|
|
|
if (Type == WeaponType.Axe)
|
|
{
|
|
attacker.CheckSkill(SkillName.Lumberjacking, 0.0, 100.0); // Passively check Lumberjacking for gain
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 = GetBonus(attacker.Skills.Lumberjacking.Value, 0.200, 100.0, 10.00);
|
|
|
|
if (Type != WeaponType.Axe)
|
|
{
|
|
lumberBonus = 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.
|
|
if (DivineFurySpell.UnderEffect(attacker))
|
|
{
|
|
damageBonus += 10;
|
|
}
|
|
|
|
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 +
|
|
(GetDamageBonus() + damageBonus) / 100.0;
|
|
|
|
return damage + (int)(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)
|
|
{
|
|
attacker.CheckSkill(
|
|
SkillName.Tactics,
|
|
0.0,
|
|
attacker.Skills.Tactics.Cap
|
|
); // Passively check tactics for gain
|
|
attacker.CheckSkill(
|
|
SkillName.Anatomy,
|
|
0.0,
|
|
attacker.Skills.Anatomy.Cap
|
|
); // Passively check Anatomy for gain
|
|
|
|
if (Type == WeaponType.Axe)
|
|
{
|
|
attacker.CheckSkill(SkillName.Lumberjacking, 0.0, 100.0); // Passively check Lumberjacking for gain
|
|
}
|
|
}
|
|
|
|
/* 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 (m_Quality != WeaponQuality.Regular)
|
|
{
|
|
modifiers += ((int)m_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 (m_MaxHits > 0 && m_Hits < m_MaxHits)
|
|
{
|
|
scale = 50 + 50 * m_Hits / m_MaxHits;
|
|
}
|
|
|
|
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 PlayerMobile))
|
|
{
|
|
damage = (int)(damage / 2.0);
|
|
}
|
|
|
|
return damage;
|
|
}
|
|
|
|
public virtual void PlayHurtAnimation(Mobile from)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (from.Mounted)
|
|
{
|
|
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);
|
|
}
|
|
|
|
private string GetNameString() => Name ?? $"#{LabelNumber}";
|
|
|
|
public int GetElementalDamageHue()
|
|
{
|
|
GetDamageTypes(null, out _, out var fire, out var cold, out var pois, out var nrgy, out _, out _);
|
|
// Order is Cold, Energy, Fire, Poison, Physical left
|
|
|
|
var currentMax = 50;
|
|
var hue = 0;
|
|
|
|
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(ObjectPropertyList list)
|
|
{
|
|
var oreType = m_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
|
|
};
|
|
|
|
if (oreType != 0)
|
|
{
|
|
list.Add(1053099, "#{0}\t{1}", oreType, GetNameString()); // ~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 imgame
|
|
* 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(m_EngravedText))
|
|
{
|
|
list.Add(1062613, m_EngravedText);
|
|
}
|
|
|
|
/* list.Add( 1062613, Utility.FixHtml( m_EngravedText ) ); */
|
|
}
|
|
|
|
public override bool AllowEquippedCast(Mobile from)
|
|
{
|
|
if (base.AllowEquippedCast(from))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return Attributes.SpellChanneling != 0;
|
|
}
|
|
|
|
public virtual int GetLuckBonus()
|
|
{
|
|
var resInfo = CraftResources.GetInfo(m_Resource);
|
|
|
|
var attrInfo = resInfo?.AttributeInfo;
|
|
|
|
if (attrInfo == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return attrInfo.WeaponLuck;
|
|
}
|
|
|
|
public override void GetProperties(ObjectPropertyList list)
|
|
{
|
|
base.GetProperties(list);
|
|
|
|
if (m_Crafter != null)
|
|
{
|
|
list.Add(1050043, m_Crafter.Name); // crafted by ~1_NAME~
|
|
}
|
|
|
|
if (m_FactionState != null)
|
|
{
|
|
list.Add(1041350); // faction item
|
|
}
|
|
|
|
SkillBonuses?.GetProperties(list);
|
|
|
|
if (m_Quality == WeaponQuality.Exceptional)
|
|
{
|
|
list.Add(1060636); // exceptional
|
|
}
|
|
|
|
if (RequiredRace == Race.Elf)
|
|
{
|
|
list.Add(1075086); // Elves Only
|
|
}
|
|
|
|
if (ArtifactRarity > 0)
|
|
{
|
|
list.Add(1061078, ArtifactRarity.ToString()); // artifact rarity ~1_val~
|
|
}
|
|
|
|
if (this is IUsesRemaining usesRemaining && usesRemaining.ShowUsesRemaining)
|
|
{
|
|
list.Add(1060584, usesRemaining.UsesRemaining.ToString()); // uses remaining: ~1_val~
|
|
}
|
|
|
|
if (m_Poison != null && m_PoisonCharges > 0)
|
|
{
|
|
list.Add(1062412 + m_Poison.Level, m_PoisonCharges.ToString());
|
|
}
|
|
|
|
if (m_Slayer != SlayerName.None)
|
|
{
|
|
var entry = SlayerGroup.GetEntryByName(m_Slayer);
|
|
if (entry != null)
|
|
{
|
|
list.Add(entry.Title);
|
|
}
|
|
}
|
|
|
|
if (m_Slayer2 != SlayerName.None)
|
|
{
|
|
var entry = SlayerGroup.GetEntryByName(m_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.ToString()); // damage increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = Attributes.DefendChance) != 0)
|
|
{
|
|
list.Add(1060408, prop.ToString()); // defense chance increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = Attributes.EnhancePotions) != 0)
|
|
{
|
|
list.Add(1060411, prop.ToString()); // enhance potions ~1_val~%
|
|
}
|
|
|
|
if ((prop = Attributes.CastRecovery) != 0)
|
|
{
|
|
list.Add(1060412, prop.ToString()); // faster cast recovery ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.CastSpeed) != 0)
|
|
{
|
|
list.Add(1060413, prop.ToString()); // faster casting ~1_val~
|
|
}
|
|
|
|
if ((prop = GetHitChanceBonus() + Attributes.AttackChance) != 0)
|
|
{
|
|
list.Add(1060415, prop.ToString()); // hit chance increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitColdArea) != 0)
|
|
{
|
|
list.Add(1060416, prop.ToString()); // hit cold area ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitDispel) != 0)
|
|
{
|
|
list.Add(1060417, prop.ToString()); // hit dispel ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitEnergyArea) != 0)
|
|
{
|
|
list.Add(1060418, prop.ToString()); // hit energy area ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitFireArea) != 0)
|
|
{
|
|
list.Add(1060419, prop.ToString()); // hit fire area ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitFireball) != 0)
|
|
{
|
|
list.Add(1060420, prop.ToString()); // hit fireball ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitHarm) != 0)
|
|
{
|
|
list.Add(1060421, prop.ToString()); // hit harm ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitLeechHits) != 0)
|
|
{
|
|
list.Add(1060422, prop.ToString()); // hit life leech ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitLightning) != 0)
|
|
{
|
|
list.Add(1060423, prop.ToString()); // hit lightning ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitLowerAttack) != 0)
|
|
{
|
|
list.Add(1060424, prop.ToString()); // hit lower attack ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitLowerDefend) != 0)
|
|
{
|
|
list.Add(1060425, prop.ToString()); // hit lower defense ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitMagicArrow) != 0)
|
|
{
|
|
list.Add(1060426, prop.ToString()); // hit magic arrow ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitLeechMana) != 0)
|
|
{
|
|
list.Add(1060427, prop.ToString()); // hit mana leech ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitPhysicalArea) != 0)
|
|
{
|
|
list.Add(1060428, prop.ToString()); // hit physical area ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitPoisonArea) != 0)
|
|
{
|
|
list.Add(1060429, prop.ToString()); // hit poison area ~1_val~%
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.HitLeechStam) != 0)
|
|
{
|
|
list.Add(1060430, prop.ToString()); // hit stamina leech ~1_val~%
|
|
}
|
|
|
|
if (ImmolatingWeaponSpell.IsImmolating(this))
|
|
{
|
|
list.Add(1111917); // Immolated
|
|
}
|
|
|
|
if (Core.ML && (ranged?.Velocity ?? 0) != 0)
|
|
{
|
|
list.Add(1072793, prop.ToString()); // Velocity ~1_val~%
|
|
}
|
|
|
|
if ((prop = Attributes.BonusDex) != 0)
|
|
{
|
|
list.Add(1060409, prop.ToString()); // dexterity bonus ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.BonusHits) != 0)
|
|
{
|
|
list.Add(1060431, prop.ToString()); // hit point increase ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.BonusInt) != 0)
|
|
{
|
|
list.Add(1060432, prop.ToString()); // intelligence bonus ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.LowerManaCost) != 0)
|
|
{
|
|
list.Add(1060433, prop.ToString()); // lower mana cost ~1_val~%
|
|
}
|
|
|
|
if ((prop = Attributes.LowerRegCost) != 0)
|
|
{
|
|
list.Add(1060434, prop.ToString()); // lower reagent cost ~1_val~%
|
|
}
|
|
|
|
if ((prop = GetLowerStatReq()) != 0)
|
|
{
|
|
list.Add(1060435, prop.ToString()); // lower requirements ~1_val~%
|
|
}
|
|
|
|
if ((prop = GetLuckBonus() + Attributes.Luck) != 0)
|
|
{
|
|
list.Add(1060436, prop.ToString()); // luck ~1_val~
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.MageWeapon) != 0)
|
|
{
|
|
list.Add(1060438, (30 - prop).ToString()); // mage weapon -~1_val~ skill
|
|
}
|
|
|
|
if ((prop = Attributes.BonusMana) != 0)
|
|
{
|
|
list.Add(1060439, prop.ToString()); // mana increase ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.RegenMana) != 0)
|
|
{
|
|
list.Add(1060440, prop.ToString()); // mana regeneration ~1_val~
|
|
}
|
|
|
|
if (Attributes.NightSight != 0)
|
|
{
|
|
list.Add(1060441); // night sight
|
|
}
|
|
|
|
if ((prop = Attributes.ReflectPhysical) != 0)
|
|
{
|
|
list.Add(1060442, prop.ToString()); // reflect physical damage ~1_val~%
|
|
}
|
|
|
|
if ((prop = Attributes.RegenStam) != 0)
|
|
{
|
|
list.Add(1060443, prop.ToString()); // stamina regeneration ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.RegenHits) != 0)
|
|
{
|
|
list.Add(1060444, prop.ToString()); // hit point regeneration ~1_val~
|
|
}
|
|
|
|
if ((prop = WeaponAttributes.SelfRepair) != 0)
|
|
{
|
|
list.Add(1060450, prop.ToString()); // self repair ~1_val~
|
|
}
|
|
|
|
if (Attributes.SpellChanneling != 0)
|
|
{
|
|
list.Add(1060482); // spell channeling
|
|
}
|
|
|
|
if ((prop = Attributes.SpellDamage) != 0)
|
|
{
|
|
list.Add(1060483, prop.ToString()); // spell damage increase ~1_val~%
|
|
}
|
|
|
|
if ((prop = Attributes.BonusStam) != 0)
|
|
{
|
|
list.Add(1060484, prop.ToString()); // stamina increase ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.BonusStr) != 0)
|
|
{
|
|
list.Add(1060485, prop.ToString()); // strength bonus ~1_val~
|
|
}
|
|
|
|
if ((prop = Attributes.WeaponSpeed) != 0)
|
|
{
|
|
list.Add(1060486, prop.ToString()); // swing speed increase ~1_val~%
|
|
}
|
|
|
|
if (Core.ML && (prop = Attributes.IncreasedKarmaLoss) != 0)
|
|
{
|
|
list.Add(1075210, prop.ToString()); // 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.ToString()); // physical damage ~1_val~%
|
|
}
|
|
|
|
if (fire != 0)
|
|
{
|
|
list.Add(1060405, fire.ToString()); // fire damage ~1_val~%
|
|
}
|
|
|
|
if (cold != 0)
|
|
{
|
|
list.Add(1060404, cold.ToString()); // cold damage ~1_val~%
|
|
}
|
|
|
|
if (pois != 0)
|
|
{
|
|
list.Add(1060406, pois.ToString()); // poison damage ~1_val~%
|
|
}
|
|
|
|
if (nrgy != 0)
|
|
{
|
|
list.Add(1060407, nrgy.ToString()); // energy damage ~1_val
|
|
}
|
|
|
|
if (Core.ML && chaos != 0)
|
|
{
|
|
list.Add(1072846, chaos.ToString()); // chaos damage ~1_val~%
|
|
}
|
|
|
|
if (Core.ML && direct != 0)
|
|
{
|
|
list.Add(1079978, direct.ToString()); // Direct Damage: ~1_PERCENT~%
|
|
}
|
|
|
|
list.Add(1061168, "{0}\t{1}", MinDamage.ToString(), MaxDamage.ToString()); // weapon damage ~1_val~ - ~2_val~
|
|
|
|
if (Core.ML)
|
|
{
|
|
list.Add(1061167, $"{Speed}s"); // weapon speed ~1_val~
|
|
}
|
|
else
|
|
{
|
|
list.Add(1061167, Speed.ToString());
|
|
}
|
|
|
|
if (MaxRange > 1)
|
|
{
|
|
list.Add(1061169, MaxRange.ToString()); // range ~1_val~
|
|
}
|
|
|
|
var strReq = AOS.Scale(StrRequirement, 100 - GetLowerStatReq());
|
|
|
|
if (strReq > 0)
|
|
{
|
|
list.Add(1061170, strReq.ToString()); // 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);
|
|
break; // skill required: swordsmanship
|
|
case SkillName.Macing:
|
|
list.Add(1061173);
|
|
break; // skill required: mace fighting
|
|
case SkillName.Fencing:
|
|
list.Add(1061174);
|
|
break; // skill required: fencing
|
|
case SkillName.Archery:
|
|
list.Add(1061175);
|
|
break; // skill required: archery
|
|
}
|
|
}
|
|
|
|
if (m_Hits >= 0 && m_MaxHits > 0)
|
|
{
|
|
list.Add(1060639, "{0}\t{1}", m_Hits, m_MaxHits); // durability ~1_val~ / ~2_val~
|
|
}
|
|
}
|
|
|
|
public override void OnSingleClick(Mobile from)
|
|
{
|
|
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 (m_Quality == WeaponQuality.Exceptional)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(1018305 - (int)m_Quality));
|
|
}
|
|
|
|
if (m_Identified || from.AccessLevel >= AccessLevel.GameMaster)
|
|
{
|
|
if (m_Slayer != SlayerName.None)
|
|
{
|
|
var entry = SlayerGroup.GetEntryByName(m_Slayer);
|
|
if (entry != null)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(entry.Title));
|
|
}
|
|
}
|
|
|
|
if (m_Slayer2 != SlayerName.None)
|
|
{
|
|
var entry = SlayerGroup.GetEntryByName(m_Slayer2);
|
|
if (entry != null)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(entry.Title));
|
|
}
|
|
}
|
|
|
|
if (m_DurabilityLevel != WeaponDurabilityLevel.Regular)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(1038000 + (int)m_DurabilityLevel));
|
|
}
|
|
|
|
if (m_DamageLevel != WeaponDamageLevel.Regular)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(1038015 + (int)m_DamageLevel));
|
|
}
|
|
|
|
if (m_AccuracyLevel != WeaponAccuracyLevel.Regular)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(1038010 + (int)m_AccuracyLevel));
|
|
}
|
|
}
|
|
else if (m_Slayer != SlayerName.None || m_Slayer2 != SlayerName.None ||
|
|
m_DurabilityLevel != WeaponDurabilityLevel.Regular || m_DamageLevel != WeaponDamageLevel.Regular ||
|
|
m_AccuracyLevel != WeaponAccuracyLevel.Regular)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(1038000)); // Unidentified
|
|
}
|
|
|
|
if (m_Poison != null && m_PoisonCharges > 0)
|
|
{
|
|
attrs.Add(new EquipInfoAttribute(1017383, m_PoisonCharges));
|
|
}
|
|
|
|
int number;
|
|
|
|
if (Name == null)
|
|
{
|
|
number = LabelNumber;
|
|
}
|
|
else
|
|
{
|
|
LabelTo(from, Name);
|
|
number = 1041000;
|
|
}
|
|
|
|
if (attrs.Count == 0 && Crafter == null && Name != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var eqInfo = new EquipmentInfo(number, m_Crafter, false, attrs.ToArray());
|
|
|
|
from.Send(new DisplayEquipmentInfo(this, eqInfo));
|
|
}
|
|
|
|
public virtual int GetHitAttackSound(Mobile attacker, Mobile defender)
|
|
{
|
|
var sound = attacker.GetAttackSound();
|
|
|
|
if (sound == -1)
|
|
{
|
|
sound = HitSound;
|
|
}
|
|
|
|
return 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);
|
|
|
|
MagerySpell 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, defender.Map, 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;
|
|
}
|
|
|
|
var range = Core.ML ? 5 : 10;
|
|
|
|
var eable = from.GetMobilesInRange(range);
|
|
var list = eable.Where(
|
|
m =>
|
|
from != m && defender != m && SpellHelper.ValidIndirectTarget(from, m)
|
|
&& from.CanBeHarmful(m, false) && (!Core.ML || from.InLOS(m))
|
|
)
|
|
.ToList();
|
|
eable.Free();
|
|
|
|
if (list.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Effects.PlaySound(from.Location, map, sound);
|
|
|
|
for (var i = 0; i < list.Count; ++i)
|
|
{
|
|
var m = list[i];
|
|
|
|
var scalar = Core.ML ? 1.0 : (11 - from.GetDistanceToSqrt(m)) / 10;
|
|
var damage = GetBaseDamage(from);
|
|
|
|
if (scalar <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
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 void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf)
|
|
{
|
|
if (setIf)
|
|
{
|
|
flags |= toSet;
|
|
}
|
|
}
|
|
|
|
private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0;
|
|
|
|
public override void Serialize(IGenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
|
|
writer.Write(9); // version
|
|
|
|
var flags = SaveFlag.None;
|
|
|
|
SetSaveFlag(ref flags, SaveFlag.DamageLevel, m_DamageLevel != WeaponDamageLevel.Regular);
|
|
SetSaveFlag(ref flags, SaveFlag.AccuracyLevel, m_AccuracyLevel != WeaponAccuracyLevel.Regular);
|
|
SetSaveFlag(ref flags, SaveFlag.DurabilityLevel, m_DurabilityLevel != WeaponDurabilityLevel.Regular);
|
|
SetSaveFlag(ref flags, SaveFlag.Quality, m_Quality != WeaponQuality.Regular);
|
|
SetSaveFlag(ref flags, SaveFlag.Hits, m_Hits != 0);
|
|
SetSaveFlag(ref flags, SaveFlag.MaxHits, m_MaxHits != 0);
|
|
SetSaveFlag(ref flags, SaveFlag.Slayer, m_Slayer != SlayerName.None);
|
|
SetSaveFlag(ref flags, SaveFlag.Poison, m_Poison != null);
|
|
SetSaveFlag(ref flags, SaveFlag.PoisonCharges, m_PoisonCharges != 0);
|
|
SetSaveFlag(ref flags, SaveFlag.Crafter, m_Crafter != null);
|
|
SetSaveFlag(ref flags, SaveFlag.Identified, m_Identified);
|
|
SetSaveFlag(ref flags, SaveFlag.StrReq, m_StrReq != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.DexReq, m_DexReq != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.IntReq, m_IntReq != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.MinDamage, m_MinDamage != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.MaxDamage, m_MaxDamage != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.HitSound, m_HitSound != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.MissSound, m_MissSound != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.Speed, m_Speed != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.MaxRange, m_MaxRange != -1);
|
|
SetSaveFlag(ref flags, SaveFlag.Skill, m_Skill != (SkillName)(-1));
|
|
SetSaveFlag(ref flags, SaveFlag.Type, m_Type != (WeaponType)(-1));
|
|
SetSaveFlag(ref flags, SaveFlag.Animation, m_Animation != (WeaponAnimation)(-1));
|
|
SetSaveFlag(ref flags, SaveFlag.Resource, m_Resource != CraftResource.Iron);
|
|
SetSaveFlag(ref flags, SaveFlag.xAttributes, !Attributes.IsEmpty);
|
|
SetSaveFlag(ref flags, SaveFlag.xWeaponAttributes, !WeaponAttributes.IsEmpty);
|
|
SetSaveFlag(ref flags, SaveFlag.PlayerConstructed, PlayerConstructed);
|
|
SetSaveFlag(ref flags, SaveFlag.SkillBonuses, !SkillBonuses.IsEmpty);
|
|
SetSaveFlag(ref flags, SaveFlag.Slayer2, m_Slayer2 != SlayerName.None);
|
|
SetSaveFlag(ref flags, SaveFlag.ElementalDamages, !AosElementDamages.IsEmpty);
|
|
SetSaveFlag(ref flags, SaveFlag.EngravedText, !string.IsNullOrEmpty(m_EngravedText));
|
|
|
|
writer.Write((int)flags);
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.DamageLevel))
|
|
{
|
|
writer.Write((int)m_DamageLevel);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.AccuracyLevel))
|
|
{
|
|
writer.Write((int)m_AccuracyLevel);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.DurabilityLevel))
|
|
{
|
|
writer.Write((int)m_DurabilityLevel);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Quality))
|
|
{
|
|
writer.Write((int)m_Quality);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Hits))
|
|
{
|
|
writer.Write(m_Hits);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MaxHits))
|
|
{
|
|
writer.Write(m_MaxHits);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Slayer))
|
|
{
|
|
writer.Write((int)m_Slayer);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Poison))
|
|
{
|
|
Poison.Serialize(m_Poison, writer);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.PoisonCharges))
|
|
{
|
|
writer.Write(m_PoisonCharges);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Crafter))
|
|
{
|
|
writer.Write(m_Crafter);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.StrReq))
|
|
{
|
|
writer.Write(m_StrReq);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.DexReq))
|
|
{
|
|
writer.Write(m_DexReq);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.IntReq))
|
|
{
|
|
writer.Write(m_IntReq);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MinDamage))
|
|
{
|
|
writer.Write(m_MinDamage);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MaxDamage))
|
|
{
|
|
writer.Write(m_MaxDamage);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.HitSound))
|
|
{
|
|
writer.Write(m_HitSound);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MissSound))
|
|
{
|
|
writer.Write(m_MissSound);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Speed))
|
|
{
|
|
writer.Write(m_Speed);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MaxRange))
|
|
{
|
|
writer.Write(m_MaxRange);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Skill))
|
|
{
|
|
writer.Write((int)m_Skill);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Type))
|
|
{
|
|
writer.Write((int)m_Type);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Animation))
|
|
{
|
|
writer.Write((int)m_Animation);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Resource))
|
|
{
|
|
writer.Write((int)m_Resource);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.xAttributes))
|
|
{
|
|
Attributes.Serialize(writer);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.xWeaponAttributes))
|
|
{
|
|
WeaponAttributes.Serialize(writer);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.SkillBonuses))
|
|
{
|
|
SkillBonuses.Serialize(writer);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Slayer2))
|
|
{
|
|
writer.Write((int)m_Slayer2);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.ElementalDamages))
|
|
{
|
|
AosElementDamages.Serialize(writer);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.EngravedText))
|
|
{
|
|
writer.Write(m_EngravedText);
|
|
}
|
|
}
|
|
|
|
public override void Deserialize(IGenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
|
|
var version = reader.ReadInt();
|
|
|
|
var parentMobile = Parent as Mobile;
|
|
|
|
switch (version)
|
|
{
|
|
case 9:
|
|
case 8:
|
|
case 7:
|
|
case 6:
|
|
case 5:
|
|
{
|
|
var flags = (SaveFlag)reader.ReadInt();
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.DamageLevel))
|
|
{
|
|
m_DamageLevel = (WeaponDamageLevel)reader.ReadInt();
|
|
|
|
if (m_DamageLevel > WeaponDamageLevel.Vanq)
|
|
{
|
|
m_DamageLevel = WeaponDamageLevel.Ruin;
|
|
}
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.AccuracyLevel))
|
|
{
|
|
m_AccuracyLevel = (WeaponAccuracyLevel)reader.ReadInt();
|
|
|
|
if (m_AccuracyLevel > WeaponAccuracyLevel.Supremely)
|
|
{
|
|
m_AccuracyLevel = WeaponAccuracyLevel.Accurate;
|
|
}
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.DurabilityLevel))
|
|
{
|
|
m_DurabilityLevel = (WeaponDurabilityLevel)reader.ReadInt();
|
|
|
|
if (m_DurabilityLevel > WeaponDurabilityLevel.Indestructible)
|
|
{
|
|
m_DurabilityLevel = WeaponDurabilityLevel.Durable;
|
|
}
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Quality))
|
|
{
|
|
m_Quality = (WeaponQuality)reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_Quality = WeaponQuality.Regular;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Hits))
|
|
{
|
|
m_Hits = reader.ReadInt();
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MaxHits))
|
|
{
|
|
m_MaxHits = reader.ReadInt();
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Slayer))
|
|
{
|
|
m_Slayer = (SlayerName)reader.ReadInt();
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Poison))
|
|
{
|
|
m_Poison = Poison.Deserialize(reader);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.PoisonCharges))
|
|
{
|
|
m_PoisonCharges = reader.ReadInt();
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Crafter))
|
|
{
|
|
m_Crafter = reader.ReadMobile();
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Identified))
|
|
{
|
|
m_Identified = version >= 6 || reader.ReadBool();
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.StrReq))
|
|
{
|
|
m_StrReq = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_StrReq = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.DexReq))
|
|
{
|
|
m_DexReq = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_DexReq = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.IntReq))
|
|
{
|
|
m_IntReq = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_IntReq = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MinDamage))
|
|
{
|
|
m_MinDamage = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_MinDamage = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MaxDamage))
|
|
{
|
|
m_MaxDamage = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_MaxDamage = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.HitSound))
|
|
{
|
|
m_HitSound = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_HitSound = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MissSound))
|
|
{
|
|
m_MissSound = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_MissSound = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Speed))
|
|
{
|
|
if (version < 9)
|
|
{
|
|
m_Speed = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_Speed = reader.ReadFloat();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Speed = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.MaxRange))
|
|
{
|
|
m_MaxRange = reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_MaxRange = -1;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Skill))
|
|
{
|
|
m_Skill = (SkillName)reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_Skill = (SkillName)(-1);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Type))
|
|
{
|
|
m_Type = (WeaponType)reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_Type = (WeaponType)(-1);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Animation))
|
|
{
|
|
m_Animation = (WeaponAnimation)reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_Animation = (WeaponAnimation)(-1);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Resource))
|
|
{
|
|
m_Resource = (CraftResource)reader.ReadInt();
|
|
}
|
|
else
|
|
{
|
|
m_Resource = CraftResource.Iron;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.xAttributes))
|
|
{
|
|
Attributes = new AosAttributes(this, reader);
|
|
}
|
|
else
|
|
{
|
|
Attributes = new AosAttributes(this);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.xWeaponAttributes))
|
|
{
|
|
WeaponAttributes = new AosWeaponAttributes(this, reader);
|
|
}
|
|
else
|
|
{
|
|
WeaponAttributes = new AosWeaponAttributes(this);
|
|
}
|
|
|
|
if (UseSkillMod && m_AccuracyLevel != WeaponAccuracyLevel.Regular && parentMobile != null)
|
|
{
|
|
m_SkillMod = new DefaultSkillMod(AccuracySkill, true, (int)m_AccuracyLevel * 5);
|
|
parentMobile.AddSkillMod(m_SkillMod);
|
|
}
|
|
|
|
if (version < 7 && WeaponAttributes.MageWeapon != 0)
|
|
{
|
|
WeaponAttributes.MageWeapon = 30 - WeaponAttributes.MageWeapon;
|
|
}
|
|
|
|
if (Core.AOS && WeaponAttributes.MageWeapon != 0 && WeaponAttributes.MageWeapon != 30 &&
|
|
parentMobile != null)
|
|
{
|
|
m_MageMod = new DefaultSkillMod(SkillName.Magery, true, -30 + WeaponAttributes.MageWeapon);
|
|
parentMobile.AddSkillMod(m_MageMod);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.PlayerConstructed))
|
|
{
|
|
PlayerConstructed = true;
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.SkillBonuses))
|
|
{
|
|
SkillBonuses = new AosSkillBonuses(this, reader);
|
|
}
|
|
else
|
|
{
|
|
SkillBonuses = new AosSkillBonuses(this);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.Slayer2))
|
|
{
|
|
m_Slayer2 = (SlayerName)reader.ReadInt();
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.ElementalDamages))
|
|
{
|
|
AosElementDamages = new AosElementAttributes(this, reader);
|
|
}
|
|
else
|
|
{
|
|
AosElementDamages = new AosElementAttributes(this);
|
|
}
|
|
|
|
if (GetSaveFlag(flags, SaveFlag.EngravedText))
|
|
{
|
|
m_EngravedText = reader.ReadString();
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
m_Slayer = (SlayerName)reader.ReadInt();
|
|
|
|
goto case 3;
|
|
}
|
|
case 3:
|
|
{
|
|
m_StrReq = reader.ReadInt();
|
|
m_DexReq = reader.ReadInt();
|
|
m_IntReq = reader.ReadInt();
|
|
|
|
goto case 2;
|
|
}
|
|
case 2:
|
|
{
|
|
m_Identified = reader.ReadBool();
|
|
|
|
goto case 1;
|
|
}
|
|
case 1:
|
|
{
|
|
m_MaxRange = reader.ReadInt();
|
|
|
|
goto case 0;
|
|
}
|
|
case 0:
|
|
{
|
|
if (version == 0)
|
|
{
|
|
m_MaxRange = 1; // default
|
|
}
|
|
|
|
if (version < 5)
|
|
{
|
|
m_Resource = CraftResource.Iron;
|
|
Attributes = new AosAttributes(this);
|
|
WeaponAttributes = new AosWeaponAttributes(this);
|
|
AosElementDamages = new AosElementAttributes(this);
|
|
SkillBonuses = new AosSkillBonuses(this);
|
|
}
|
|
|
|
m_MinDamage = reader.ReadInt();
|
|
m_MaxDamage = reader.ReadInt();
|
|
|
|
m_Speed = reader.ReadInt();
|
|
|
|
m_HitSound = reader.ReadInt();
|
|
m_MissSound = reader.ReadInt();
|
|
|
|
m_Skill = (SkillName)reader.ReadInt();
|
|
m_Type = (WeaponType)reader.ReadInt();
|
|
m_Animation = (WeaponAnimation)reader.ReadInt();
|
|
m_DamageLevel = (WeaponDamageLevel)reader.ReadInt();
|
|
m_AccuracyLevel = (WeaponAccuracyLevel)reader.ReadInt();
|
|
m_DurabilityLevel = (WeaponDurabilityLevel)reader.ReadInt();
|
|
m_Quality = (WeaponQuality)reader.ReadInt();
|
|
|
|
m_Crafter = reader.ReadMobile();
|
|
|
|
m_Poison = Poison.Deserialize(reader);
|
|
m_PoisonCharges = reader.ReadInt();
|
|
|
|
if (m_StrReq == OldStrengthReq)
|
|
{
|
|
m_StrReq = -1;
|
|
}
|
|
|
|
if (m_DexReq == OldDexterityReq)
|
|
{
|
|
m_DexReq = -1;
|
|
}
|
|
|
|
if (m_IntReq == OldIntelligenceReq)
|
|
{
|
|
m_IntReq = -1;
|
|
}
|
|
|
|
if (m_MinDamage == OldMinDamage)
|
|
{
|
|
m_MinDamage = -1;
|
|
}
|
|
|
|
if (m_MaxDamage == OldMaxDamage)
|
|
{
|
|
m_MaxDamage = -1;
|
|
}
|
|
|
|
if (m_HitSound == OldHitSound)
|
|
{
|
|
m_HitSound = -1;
|
|
}
|
|
|
|
if (m_MissSound == OldMissSound)
|
|
{
|
|
m_MissSound = -1;
|
|
}
|
|
|
|
if (m_Speed == OldSpeed)
|
|
{
|
|
m_Speed = -1;
|
|
}
|
|
|
|
if (m_MaxRange == OldMaxRange)
|
|
{
|
|
m_MaxRange = -1;
|
|
}
|
|
|
|
if (m_Skill == OldSkill)
|
|
{
|
|
m_Skill = (SkillName)(-1);
|
|
}
|
|
|
|
if (m_Type == OldType)
|
|
{
|
|
m_Type = (WeaponType)(-1);
|
|
}
|
|
|
|
if (m_Animation == OldAnimation)
|
|
{
|
|
m_Animation = (WeaponAnimation)(-1);
|
|
}
|
|
|
|
if (UseSkillMod && m_AccuracyLevel != WeaponAccuracyLevel.Regular && parentMobile != null)
|
|
{
|
|
m_SkillMod = new DefaultSkillMod(AccuracySkill, true, (int)m_AccuracyLevel * 5);
|
|
parentMobile.AddSkillMod(m_SkillMod);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
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))
|
|
{
|
|
var modName = Serial.ToString();
|
|
|
|
if (strBonus != 0)
|
|
{
|
|
parentMobile.AddStatMod(new StatMod(StatType.Str, $"{modName}Str", strBonus, TimeSpan.Zero));
|
|
}
|
|
|
|
if (dexBonus != 0)
|
|
{
|
|
parentMobile.AddStatMod(new StatMod(StatType.Dex, $"{modName}Dex", dexBonus, TimeSpan.Zero));
|
|
}
|
|
|
|
if (intBonus != 0)
|
|
{
|
|
parentMobile.AddStatMod(new StatMod(StatType.Int, $"{modName}Int", intBonus, TimeSpan.Zero));
|
|
}
|
|
}
|
|
|
|
parentMobile?.CheckStatTimers();
|
|
|
|
if (m_Hits <= 0 && m_MaxHits <= 0)
|
|
{
|
|
m_Hits = m_MaxHits = Utility.RandomMinMax(InitMinHits, InitMaxHits);
|
|
}
|
|
|
|
if (version < 6)
|
|
{
|
|
PlayerConstructed = true; // we don't know, so, assume it's crafted
|
|
}
|
|
}
|
|
|
|
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 SaveFlag
|
|
{
|
|
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,
|
|
xAttributes = 0x01000000,
|
|
xWeaponAttributes = 0x02000000,
|
|
PlayerConstructed = 0x04000000,
|
|
SkillBonuses = 0x08000000,
|
|
Slayer2 = 0x10000000,
|
|
ElementalDamages = 0x20000000,
|
|
EngravedText = 0x40000000
|
|
}
|
|
}
|
|
|
|
public enum CheckSlayerResult
|
|
{
|
|
None,
|
|
Slayer,
|
|
Opposition
|
|
}
|
|
}
|