v0.0.1-Alpha (#42)
This commit is contained in:
parent
d30f93c454
commit
a36796c2c1
3552 changed files with 2476 additions and 15223 deletions
28
Projects/Scripts/Items/Weapons/Abilities/ArmorIgnore.cs
Normal file
28
Projects/Scripts/Items/Weapons/Abilities/ArmorIgnore.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// This special move allows the skilled warrior to bypass his target's physical resistance, for one shot only.
|
||||
/// The Armor Ignore shot does slightly less damage than normal.
|
||||
/// Against a heavily armored opponent, this ability is a big win, but when used against a very lightly armored foe, it might
|
||||
/// be better to use a standard strike!
|
||||
/// </summary>
|
||||
public class ArmorIgnore : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 30;
|
||||
public override double DamageScalar => 0.9;
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060076); // Your attack penetrates their armor!
|
||||
defender.SendLocalizedMessage(1060077); // The blow penetrated your armor!
|
||||
|
||||
defender.PlaySound(0x56);
|
||||
defender.FixedParticles(0x3728, 200, 25, 9942, EffectLayer.Waist);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Projects/Scripts/Items/Weapons/Abilities/ArmorPierce.cs
Normal file
39
Projects/Scripts/Items/Weapons/Abilities/ArmorPierce.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Strike your opponent with great force, partially bypassing their armor and inflicting greater damage. Requires either
|
||||
/// Bushido or Ninjitsu skill
|
||||
/// </summary>
|
||||
public class ArmorPierce : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 30;
|
||||
public override double DamageScalar => 1.5;
|
||||
|
||||
public override bool RequiresSE => true;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063347,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1063350); // You pierce your opponent's armor!
|
||||
defender.SendLocalizedMessage(1063351); // Your attacker pierced your armor!
|
||||
|
||||
defender.FixedParticles(0x3728, 1, 26, 0x26D6, 0, 0, EffectLayer.Waist);
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Projects/Scripts/Items/Weapons/Abilities/BleedAttack.cs
Normal file
123
Projects/Scripts/Items/Weapons/Abilities/BleedAttack.cs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
using Server.Spells;
|
||||
using Server.Spells.Necromancy;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Make your opponent bleed profusely with this wicked use of your weapon.
|
||||
/// When successful, the target will bleed for several seconds, taking damage as time passes for up to ten seconds.
|
||||
/// The rate of damage slows down as time passes, and the blood loss can be completely staunched with the use of bandages.
|
||||
/// </summary>
|
||||
public class BleedAttack : WeaponAbility
|
||||
{
|
||||
private static Dictionary<Mobile, Timer> m_Table = new Dictionary<Mobile, Timer>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
// Necromancers under Lich or Wraith Form are immune to Bleed Attacks.
|
||||
TransformContext context = TransformationSpellHelper.GetContext(defender);
|
||||
|
||||
if (context != null && (context.Type == typeof(LichFormSpell) || context.Type == typeof(WraithFormSpell)) ||
|
||||
defender is BaseCreature creature && creature.BleedImmune)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1062052); // Your target is not affected by the bleed attack!
|
||||
return;
|
||||
}
|
||||
|
||||
attacker.SendLocalizedMessage(1060159); // Your target is bleeding!
|
||||
defender.SendLocalizedMessage(1060160); // You are bleeding!
|
||||
|
||||
if (defender is PlayerMobile)
|
||||
{
|
||||
defender.LocalOverheadMessage(MessageType.Regular, 0x21, 1060757); // You are bleeding profusely
|
||||
defender.NonlocalOverheadMessage(MessageType.Regular, 0x21, 1060758,
|
||||
defender.Name); // ~1_NAME~ is bleeding profusely
|
||||
}
|
||||
|
||||
defender.PlaySound(0x133);
|
||||
defender.FixedParticles(0x377A, 244, 25, 9950, 31, 0, EffectLayer.Waist);
|
||||
|
||||
BeginBleed(defender, attacker);
|
||||
}
|
||||
|
||||
public static bool IsBleeding(Mobile m)
|
||||
{
|
||||
return m_Table.ContainsKey(m);
|
||||
}
|
||||
|
||||
public static void BeginBleed(Mobile m, Mobile from)
|
||||
{
|
||||
m_Table.TryGetValue(m, out Timer t);
|
||||
t?.Stop();
|
||||
|
||||
m_Table[m] = t = new InternalTimer(from, m);
|
||||
t.Start();
|
||||
}
|
||||
|
||||
public static void DoBleed(Mobile m, Mobile from, int level)
|
||||
{
|
||||
if (m.Alive)
|
||||
{
|
||||
int damage = Utility.RandomMinMax(level, level * 2);
|
||||
|
||||
if (!m.Player)
|
||||
damage *= 2;
|
||||
|
||||
m.PlaySound(0x133);
|
||||
m.Damage(damage, from);
|
||||
|
||||
Blood blood = new Blood { ItemID = Utility.Random(0x122A, 5) };
|
||||
blood.MoveToWorld(m.Location, m.Map);
|
||||
}
|
||||
else
|
||||
{
|
||||
EndBleed(m, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EndBleed(Mobile m, bool message)
|
||||
{
|
||||
if (!m_Table.TryGetValue(m, out Timer t))
|
||||
return;
|
||||
|
||||
t.Stop();
|
||||
m_Table.Remove(m);
|
||||
|
||||
if (message)
|
||||
m.SendLocalizedMessage(1060167); // The bleeding wounds have healed, you are no longer bleeding!
|
||||
}
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
private int m_Count;
|
||||
private Mobile m_From;
|
||||
private Mobile m_Mobile;
|
||||
|
||||
public InternalTimer(Mobile from, Mobile m) : base(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0))
|
||||
{
|
||||
m_From = from;
|
||||
m_Mobile = m;
|
||||
Priority = TimerPriority.TwoFiftyMS;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
DoBleed(m_Mobile, m_From, 5 - m_Count);
|
||||
|
||||
if (++m_Count == 5)
|
||||
EndBleed(m_Mobile, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Projects/Scripts/Items/Weapons/Abilities/Block.cs
Normal file
97
Projects/Scripts/Items/Weapons/Abilities/Block.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Raises your defenses for a short time. Requires Bushido or Ninjitsu skill.
|
||||
/// </summary>
|
||||
public class Block : WeaponAbility
|
||||
{
|
||||
private static Dictionary<Mobile, BlockInfo> m_Table = new Dictionary<Mobile, BlockInfo>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063347,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1063345); // You block an attack!
|
||||
defender.SendLocalizedMessage(1063346); // Your attack was blocked!
|
||||
|
||||
attacker.FixedParticles(0x37C4, 1, 16, 0x251D, 0x39D, 0x3, EffectLayer.RightHand);
|
||||
|
||||
int bonus = (int)(10.0 * ((Math.Max(attacker.Skills.Bushido.Value,
|
||||
attacker.Skills.Ninjitsu.Value) - 50.0) / 70.0 + 5));
|
||||
|
||||
BeginBlock(attacker, bonus);
|
||||
}
|
||||
|
||||
public static bool GetBonus(Mobile targ, ref int bonus)
|
||||
{
|
||||
if (!m_Table.TryGetValue(targ, out BlockInfo info))
|
||||
return false;
|
||||
|
||||
bonus = info.m_Bonus;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void BeginBlock(Mobile m, int bonus)
|
||||
{
|
||||
EndBlock(m);
|
||||
m_Table[m] = new BlockInfo(m, bonus);
|
||||
}
|
||||
|
||||
public static void EndBlock(Mobile m)
|
||||
{
|
||||
if (!m_Table.TryGetValue(m, out BlockInfo info))
|
||||
return;
|
||||
|
||||
info.m_Timer?.Stop();
|
||||
m_Table.Remove(m);
|
||||
}
|
||||
|
||||
private class BlockInfo
|
||||
{
|
||||
public int m_Bonus;
|
||||
public Timer m_Timer;
|
||||
|
||||
public BlockInfo(Mobile target, int bonus)
|
||||
{
|
||||
m_Bonus = bonus;
|
||||
m_Timer = new InternalTimer(target);
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
private Mobile m_Mobile;
|
||||
|
||||
public InternalTimer(Mobile m) : base(TimeSpan.FromSeconds(6.0))
|
||||
{
|
||||
m_Mobile = m;
|
||||
Priority = TimerPriority.TwoFiftyMS;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
EndBlock(m_Mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Projects/Scripts/Items/Weapons/Abilities/ConcussionBlow.cs
Normal file
52
Projects/Scripts/Items/Weapons/Abilities/ConcussionBlow.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// This devastating strike is most effective against those who are in good health and whose reserves of mana are low, or vice
|
||||
/// versa.
|
||||
/// </summary>
|
||||
public class ConcussionBlow : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 25;
|
||||
|
||||
public override bool OnBeforeDamage(Mobile attacker, Mobile defender)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return false;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060165); // You have delivered a concussion!
|
||||
defender.SendLocalizedMessage(1060166); // You feel disoriented!
|
||||
|
||||
defender.PlaySound(0x213);
|
||||
defender.FixedParticles(0x377A, 1, 32, 9949, 1153, 0, EffectLayer.Head);
|
||||
|
||||
Effects.SendMovingParticles(
|
||||
new Entity(Serial.Zero, new Point3D(defender.X, defender.Y, defender.Z + 10), defender.Map),
|
||||
new Entity(Serial.Zero, new Point3D(defender.X, defender.Y, defender.Z + 20), defender.Map), 0x36FE, 1, 0,
|
||||
false, false, 1133, 3, 9501, 1, 0, EffectLayer.Waist, 0x100);
|
||||
|
||||
int damage = 10; // Base damage is 10.
|
||||
|
||||
if (defender.HitsMax > 0)
|
||||
{
|
||||
double hitsPercent = defender.Hits / (double)defender.HitsMax * 100.0;
|
||||
|
||||
double manaPercent = 0;
|
||||
|
||||
if (defender.ManaMax > 0)
|
||||
manaPercent = defender.Mana / (double)defender.ManaMax * 100.0;
|
||||
|
||||
damage += Math.Min((int)(Math.Abs(hitsPercent - manaPercent) / 4), 20);
|
||||
}
|
||||
|
||||
// Total damage is 10 + (0~20) = 10~30, physical, non-resistable.
|
||||
|
||||
defender.Damage(damage, attacker);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Projects/Scripts/Items/Weapons/Abilities/CrushingBlow.cs
Normal file
31
Projects/Scripts/Items/Weapons/Abilities/CrushingBlow.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Also known as the Haymaker, this attack dramatically increases the damage done by a weapon reaching its mark.
|
||||
/// </summary>
|
||||
public class CrushingBlow : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 25;
|
||||
public override double DamageScalar => 1.5;
|
||||
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060090); // You have delivered a crushing blow!
|
||||
defender.SendLocalizedMessage(1060091); // You take extra damage from the crushing attack!
|
||||
|
||||
defender.PlaySound(0x1E1);
|
||||
defender.FixedParticles(0, 1, 0, 9946, EffectLayer.Head);
|
||||
|
||||
Effects.SendMovingParticles(
|
||||
new Entity(Serial.Zero, new Point3D(defender.X, defender.Y, defender.Z + 50), defender.Map),
|
||||
new Entity(Serial.Zero, new Point3D(defender.X, defender.Y, defender.Z + 20), defender.Map), 0xFB4, 1, 0,
|
||||
false, false, 0, 3, 9501, 1, 0, EffectLayer.Head, 0x100);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Projects/Scripts/Items/Weapons/Abilities/DefenseMastery.cs
Normal file
96
Projects/Scripts/Items/Weapons/Abilities/DefenseMastery.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Raises your physical resistance for a short time while lowering your ability to inflict damage. Requires Bushido or
|
||||
/// Ninjitsu skill.
|
||||
/// </summary>
|
||||
public class DefenseMastery : WeaponAbility
|
||||
{
|
||||
private static Dictionary<Mobile, DefenseMasteryInfo> m_Table = new Dictionary<Mobile, DefenseMasteryInfo>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063347,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1063353); // You perform a masterful defense!
|
||||
|
||||
attacker.FixedParticles(0x375A, 1, 17, 0x7F2, 0x3E8, 0x3, EffectLayer.Waist);
|
||||
|
||||
int modifier =
|
||||
(int)(30.0 *
|
||||
((Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value) -
|
||||
50.0) / 70.0));
|
||||
|
||||
if (m_Table.TryGetValue(attacker, out DefenseMasteryInfo info))
|
||||
EndDefense(info);
|
||||
|
||||
ResistanceMod mod = new ResistanceMod(ResistanceType.Physical, 50 + modifier);
|
||||
attacker.AddResistanceMod(mod);
|
||||
|
||||
info = new DefenseMasteryInfo(attacker, 80 - modifier, mod);
|
||||
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(3.0), EndDefense, info);
|
||||
|
||||
m_Table[attacker] = info;
|
||||
|
||||
attacker.Delta(MobileDelta.WeaponDamage);
|
||||
}
|
||||
|
||||
public static bool GetMalus(Mobile targ, ref int damageMalus)
|
||||
{
|
||||
if (!m_Table.TryGetValue(targ, out DefenseMasteryInfo info))
|
||||
return false;
|
||||
|
||||
damageMalus = info.m_DamageMalus;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void EndDefense(DefenseMasteryInfo info)
|
||||
{
|
||||
if (info.m_Mod != null)
|
||||
info.m_From.RemoveResistanceMod(info.m_Mod);
|
||||
|
||||
info.m_Timer?.Stop();
|
||||
|
||||
// No message is sent to the player.
|
||||
|
||||
m_Table.Remove(info.m_From);
|
||||
|
||||
info.m_From.Delta(MobileDelta.WeaponDamage);
|
||||
}
|
||||
|
||||
private class DefenseMasteryInfo
|
||||
{
|
||||
public int m_DamageMalus;
|
||||
public Mobile m_From;
|
||||
public ResistanceMod m_Mod;
|
||||
public Timer m_Timer;
|
||||
|
||||
public DefenseMasteryInfo(Mobile from, int damageMalus, ResistanceMod mod)
|
||||
{
|
||||
m_From = from;
|
||||
m_DamageMalus = damageMalus;
|
||||
m_Mod = mod;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Projects/Scripts/Items/Weapons/Abilities/Disarm.cs
Normal file
78
Projects/Scripts/Items/Weapons/Abilities/Disarm.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// This attack allows you to disarm your foe.
|
||||
/// Now in Age of Shadows, a successful Disarm leaves the victim unable to re-arm another weapon for several seconds.
|
||||
/// </summary>
|
||||
public class Disarm : WeaponAbility
|
||||
{
|
||||
public static readonly TimeSpan BlockEquipDuration = TimeSpan.FromSeconds(5.0);
|
||||
|
||||
public override int BaseMana => 20;
|
||||
|
||||
// No longer active in pub21:
|
||||
/*public override bool CheckSkills( Mobile from )
|
||||
{
|
||||
if ( !base.CheckSkills( from ) )
|
||||
return false;
|
||||
|
||||
if ( !(from.Weapon is Fists) )
|
||||
return true;
|
||||
|
||||
Skill skill = from.Skills.ArmsLore;
|
||||
|
||||
if ( skill?.Base >= 80.0 )
|
||||
return true;
|
||||
|
||||
from.SendLocalizedMessage( 1061812 ); // You lack the required skill in armslore to perform that attack!
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
public override bool RequiresTactics(Mobile from)
|
||||
{
|
||||
if (!(from.Weapon is BaseWeapon weapon))
|
||||
return false;
|
||||
|
||||
return weapon.Skill != SkillName.Wrestling;
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
Item toDisarm = defender.FindItemOnLayer(Layer.OneHanded);
|
||||
|
||||
if (toDisarm?.Movable == false)
|
||||
toDisarm = defender.FindItemOnLayer(Layer.TwoHanded);
|
||||
|
||||
Container pack = defender.Backpack;
|
||||
|
||||
if (pack == null || toDisarm?.Movable == false)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
|
||||
}
|
||||
else if (!Core.ML && toDisarm == null || toDisarm is BaseShield || toDisarm is Spellbook)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1060849); // Your target is already unarmed!
|
||||
}
|
||||
else if (CheckMana(attacker, true))
|
||||
{
|
||||
attacker.SendLocalizedMessage(1060092); // You disarm their weapon!
|
||||
defender.SendLocalizedMessage(1060093); // Your weapon has been disarmed!
|
||||
|
||||
defender.PlaySound(0x3B9);
|
||||
defender.FixedParticles(0x37BE, 232, 25, 9948, EffectLayer.LeftHand);
|
||||
|
||||
pack.DropItem(toDisarm);
|
||||
|
||||
BaseWeapon.BlockEquip(defender, BlockEquipDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Projects/Scripts/Items/Weapons/Abilities/Dismount.cs
Normal file
94
Projects/Scripts/Items/Weapons/Abilities/Dismount.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using Server.Mobiles;
|
||||
using Server.Spells.Ninjitsu;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Perfect for the foot-soldier, the Dismount special attack can unseat a mounted opponent.
|
||||
/// The fighter using this ability must be on his own two feet and not in the saddle of a steed
|
||||
/// (with one exception: players may use a lance to dismount other players while mounted).
|
||||
/// If it works, the target will be knocked off his own mount and will take some extra damage from the fall!
|
||||
/// </summary>
|
||||
public class Dismount : WeaponAbility
|
||||
{
|
||||
public static readonly TimeSpan RemountDelay = TimeSpan.FromSeconds(10.0);
|
||||
|
||||
public override int BaseMana => 20;
|
||||
|
||||
public override bool Validate(Mobile from)
|
||||
{
|
||||
if (!base.Validate(from))
|
||||
return false;
|
||||
|
||||
if (from.Mounted && !(from.Weapon is Lance))
|
||||
{
|
||||
from.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted!
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker))
|
||||
return;
|
||||
|
||||
if (defender is ChaosDragoon || defender is ChaosDragoonElite)
|
||||
return;
|
||||
|
||||
if (attacker.Mounted && (!(attacker.Weapon is Lance) || !(defender.Weapon is Lance))
|
||||
) // TODO: Should there be a message here?
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
IMount mount = defender.Mount;
|
||||
|
||||
if (mount == null && !AnimalForm.UnderTransformation(defender))
|
||||
{
|
||||
attacker.SendLocalizedMessage(1060848); // This attack only works on mounted targets
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
if (Core.ML && attacker is LesserHiryu && 0.8 >= Utility.RandomDouble())
|
||||
return; //Lesser Hiryu have an 80% chance of missing this attack
|
||||
|
||||
attacker.SendLocalizedMessage(1060082); // The force of your attack has dislodged them from their mount!
|
||||
|
||||
if (attacker.Mounted)
|
||||
defender.SendLocalizedMessage(1062315); // You fall off your mount!
|
||||
else
|
||||
defender.SendLocalizedMessage(1060083); // You fall off of your mount and take damage!
|
||||
|
||||
defender.PlaySound(0x140);
|
||||
defender.FixedParticles(0x3728, 10, 15, 9955, EffectLayer.Waist);
|
||||
|
||||
if (defender is PlayerMobile mobile)
|
||||
{
|
||||
if (AnimalForm.UnderTransformation(mobile))
|
||||
mobile.SendLocalizedMessage(1114066, attacker.Name); // ~1_NAME~ knocked you out of animal form!
|
||||
else if (mobile.Mounted) mobile.SendLocalizedMessage(1040023); // You have been knocked off of your mount!
|
||||
|
||||
mobile.SetMountBlock(BlockMountType.Dazed, TimeSpan.FromSeconds(10), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
defender.Mount.Rider = null;
|
||||
}
|
||||
|
||||
if (attacker is PlayerMobile playerMobile)
|
||||
playerMobile.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, true);
|
||||
else if (Core.ML && attacker is BaseCreature bc)
|
||||
if (bc.ControlMaster is PlayerMobile pm)
|
||||
pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
|
||||
|
||||
if (!attacker.Mounted)
|
||||
AOS.Damage(defender, attacker, Utility.RandomMinMax(15, 25), 100, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Projects/Scripts/Items/Weapons/Abilities/Disrobe.cs
Normal file
45
Projects/Scripts/Items/Weapons/Abilities/Disrobe.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// This attack allows you to disrobe your foe.
|
||||
/// </summary>
|
||||
public class Disrobe : WeaponAbility
|
||||
{
|
||||
public static readonly TimeSpan BlockEquipDuration = TimeSpan.FromSeconds(5.0);
|
||||
|
||||
public override int BaseMana => 20; // Not Sure what amount of mana a creature uses.
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
Item toDisrobe = defender.FindItemOnLayer(Layer.InnerTorso);
|
||||
|
||||
if (toDisrobe?.Movable == false)
|
||||
toDisrobe = defender.FindItemOnLayer(Layer.OuterTorso);
|
||||
|
||||
Container pack = defender.Backpack;
|
||||
|
||||
if (pack == null || toDisrobe?.Movable == false)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
|
||||
}
|
||||
else if (CheckMana(attacker, true))
|
||||
{
|
||||
//attacker.SendLocalizedMessage( 1060092 ); // You disarm their weapon!
|
||||
defender.SendLocalizedMessage(1062002); // You can no longer wear your ~1_ARMOR~
|
||||
|
||||
defender.PlaySound(0x3B9);
|
||||
//defender.FixedParticles( 0x37BE, 232, 25, 9948, EffectLayer.InnerTorso );
|
||||
|
||||
pack.DropItem(toDisrobe);
|
||||
|
||||
BaseWeapon.BlockEquip(defender, BlockEquipDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Projects/Scripts/Items/Weapons/Abilities/DoubleShot.cs
Normal file
60
Projects/Scripts/Items/Weapons/Abilities/DoubleShot.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Send two arrows flying at your opponent if you're mounted. Requires Bushido or Ninjitsu skill.
|
||||
/// </summary>
|
||||
public class DoubleShot : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063347,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
Use(attacker, defender);
|
||||
}
|
||||
|
||||
public override void OnMiss(Mobile attacker, Mobile defender)
|
||||
{
|
||||
Use(attacker, defender);
|
||||
}
|
||||
|
||||
public override bool Validate(Mobile from)
|
||||
{
|
||||
if (base.Validate(from))
|
||||
{
|
||||
if (from.Mounted)
|
||||
return true;
|
||||
from.SendLocalizedMessage(1070770); // You can only execute this attack while mounted!
|
||||
ClearCurrentAbility(from);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Use(Mobile attacker, Mobile defender)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true) || attacker.Weapon == null) //sanity
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1063348); // You launch two shots at once!
|
||||
defender.SendLocalizedMessage(1063349); // You're attacked with a barrage of shots!
|
||||
|
||||
defender.FixedParticles(0x37B9, 1, 19, 0x251D, EffectLayer.Waist);
|
||||
|
||||
attacker.Weapon.OnSwing(attacker, defender);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Projects/Scripts/Items/Weapons/Abilities/DoubleStrike.cs
Normal file
46
Projects/Scripts/Items/Weapons/Abilities/DoubleStrike.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// The highly skilled warrior can use this special attack to make two quick swings in succession.
|
||||
/// Landing both blows would be devastating!
|
||||
/// </summary>
|
||||
public class DoubleStrike : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 30;
|
||||
public override double DamageScalar => 0.9;
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060084); // You attack with lightning speed!
|
||||
defender.SendLocalizedMessage(1060085); // Your attacker strikes with lightning speed!
|
||||
|
||||
defender.PlaySound(0x3BB);
|
||||
defender.FixedEffect(0x37B9, 244, 25);
|
||||
|
||||
// Swing again:
|
||||
|
||||
// If no combatant, wrong map, one of us is a ghost, or cannot see, or deleted, then stop combat
|
||||
if (defender.Deleted || attacker.Deleted || defender.Map != attacker.Map ||
|
||||
!defender.Alive || !attacker.Alive || !attacker.CanSee(defender))
|
||||
{
|
||||
attacker.Combatant = null;
|
||||
return;
|
||||
}
|
||||
|
||||
IWeapon weapon = attacker.Weapon;
|
||||
|
||||
if (!(weapon != null && attacker.InRange(defender, weapon.MaxRange) &&attacker.InLOS(defender)))
|
||||
return;
|
||||
|
||||
BaseWeapon.InDoubleStrike = true;
|
||||
attacker.RevealingAction();
|
||||
attacker.NextCombatTime = Core.TickCount + (int)weapon.OnSwing(attacker, defender).TotalMilliseconds;
|
||||
BaseWeapon.InDoubleStrike = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Projects/Scripts/Items/Weapons/Abilities/DualWield.cs
Normal file
70
Projects/Scripts/Items/Weapons/Abilities/DualWield.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Attack faster as you swing with both weapons.
|
||||
/// </summary>
|
||||
public class DualWield : WeaponAbility
|
||||
{
|
||||
public static Dictionary<Mobile, DualWieldTimer> Registry{ get; } = new Dictionary<Mobile, DualWieldTimer>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063352,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
if (Registry.TryGetValue(attacker, out DualWieldTimer timer))
|
||||
{
|
||||
timer.Stop();
|
||||
Registry.Remove(attacker);
|
||||
}
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1063362); // You dually wield for increased speed!
|
||||
attacker.FixedParticles(0x3779, 1, 15, 0x7F6, 0x3E8, 3, EffectLayer.LeftHand);
|
||||
|
||||
timer = new DualWieldTimer(attacker,
|
||||
(int)(20.0 + 3.0 * (attacker.Skills.Ninjitsu.Value - 50.0) / 7.0)); //20-50 % increase
|
||||
|
||||
timer.Start();
|
||||
Registry.Add(attacker, timer);
|
||||
}
|
||||
|
||||
public class DualWieldTimer : Timer
|
||||
{
|
||||
private Mobile m_Owner;
|
||||
|
||||
public DualWieldTimer(Mobile owner, int bonusSwingSpeed)
|
||||
: base(TimeSpan.FromSeconds(6.0))
|
||||
{
|
||||
m_Owner = owner;
|
||||
BonusSwingSpeed = bonusSwingSpeed;
|
||||
Priority = TimerPriority.FiftyMS;
|
||||
}
|
||||
|
||||
public int BonusSwingSpeed{ get; }
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
Registry.Remove(m_Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Projects/Scripts/Items/Weapons/Abilities/Feint.cs
Normal file
73
Projects/Scripts/Items/Weapons/Abilities/Feint.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Gain a defensive advantage over your primary opponent for a short time.
|
||||
/// </summary>
|
||||
public class Feint : WeaponAbility
|
||||
{
|
||||
public static Dictionary<Mobile, FeintTimer> Registry{ get; } = new Dictionary<Mobile, FeintTimer>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063347,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
if (Registry.TryGetValue(defender, out FeintTimer timer))
|
||||
{
|
||||
timer.Stop();
|
||||
Registry.Remove(defender);
|
||||
}
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1063360); // You baffle your target with a feint!
|
||||
defender.SendLocalizedMessage(1063361); // You were deceived by an attacker's feint!
|
||||
|
||||
attacker.FixedParticles(0x3728, 1, 13, 0x7F3, 0x962, 0, EffectLayer.Waist);
|
||||
|
||||
timer = new FeintTimer(defender,
|
||||
(int)(20.0 + 3.0 * (Math.Max(attacker.Skills.Ninjitsu.Value,
|
||||
attacker.Skills.Bushido.Value) - 50.0) / 7.0)); //20-50 % decrease
|
||||
|
||||
timer.Start();
|
||||
Registry.Add(defender, timer);
|
||||
}
|
||||
|
||||
public class FeintTimer : Timer
|
||||
{
|
||||
private Mobile m_Defender;
|
||||
|
||||
public FeintTimer(Mobile defender, int swingSpeedReduction)
|
||||
: base(TimeSpan.FromSeconds(6.0))
|
||||
{
|
||||
m_Defender = defender;
|
||||
SwingSpeedReduction = swingSpeedReduction;
|
||||
Priority = TimerPriority.FiftyMS;
|
||||
}
|
||||
|
||||
public int SwingSpeedReduction{ get; }
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
Registry.Remove(m_Defender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Projects/Scripts/Items/Weapons/Abilities/FrenziedWhirlwind.cs
Normal file
133
Projects/Scripts/Items/Weapons/Abilities/FrenziedWhirlwind.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Server.Spells;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// A quick attack to all enemies in range of your weapon that causes damage over time. Requires Bushido or Ninjitsu skill.
|
||||
/// </summary>
|
||||
public class FrenziedWhirlwind : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public static Dictionary<Mobile, FrenziedWirlwindTimer> Registry{ get; } = new Dictionary<Mobile, FrenziedWirlwindTimer>();
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063347,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker)) //Mana check after check that there are targets
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
Map map = attacker.Map;
|
||||
|
||||
if (!(map != null && attacker.Weapon is BaseWeapon weapon))
|
||||
return;
|
||||
|
||||
List<Mobile> targets = attacker.GetMobilesInRange(1).Where(m =>
|
||||
m?.Deleted == false && m != defender && m != attacker && SpellHelper.ValidIndirectTarget(attacker, m) &&
|
||||
m.Map == attacker.Map && m.Alive && attacker.CanSee(m) && attacker.CanBeHarmful(m) &&
|
||||
attacker.InRange(m, weapon.MaxRange) && attacker.InLOS(m)).ToList();
|
||||
|
||||
if (targets.Count == 0 || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
attacker.FixedEffect(0x3728, 10, 15);
|
||||
attacker.PlaySound(0x2A1);
|
||||
|
||||
// 5-15 damage
|
||||
int amount = (int)(10.0 * ((Math.Max(attacker.Skills.Bushido.Value,
|
||||
attacker.Skills.Ninjitsu.Value) - 50.0) / 70.0 + 5));
|
||||
|
||||
for (int i = 0; i < targets.Count; ++i)
|
||||
{
|
||||
Mobile m = targets[i];
|
||||
attacker.DoHarmful(m, true);
|
||||
|
||||
if (Registry.TryGetValue(m, out FrenziedWirlwindTimer timer))
|
||||
{
|
||||
timer.Stop();
|
||||
Registry.Remove(m);
|
||||
}
|
||||
|
||||
timer = new FrenziedWirlwindTimer(attacker, m, amount);
|
||||
timer.Start();
|
||||
Registry.Add(m, timer);
|
||||
}
|
||||
|
||||
Timer.DelayCall(TimeSpan.FromSeconds(2.0), RepeatEffect, attacker);
|
||||
}
|
||||
|
||||
private void RepeatEffect(Mobile attacker)
|
||||
{
|
||||
attacker.FixedEffect(0x3728, 10, 15);
|
||||
attacker.PlaySound(0x2A1);
|
||||
}
|
||||
|
||||
public class FrenziedWirlwindTimer : Timer
|
||||
{
|
||||
private readonly double DamagePerTick;
|
||||
private Mobile m_Attacker;
|
||||
private double m_DamageRemaining;
|
||||
private double m_DamageToDo;
|
||||
private Mobile m_Defender;
|
||||
|
||||
public FrenziedWirlwindTimer(Mobile attacker, Mobile defender, int totalDamage)
|
||||
: base(TimeSpan.Zero, TimeSpan.FromSeconds(0.25),
|
||||
12) // 3 seconds at .25 seconds apart = 12. Confirm delay in between of .25 each.
|
||||
{
|
||||
m_Attacker = attacker;
|
||||
m_Defender = defender;
|
||||
|
||||
m_DamageRemaining = totalDamage;
|
||||
DamagePerTick = (double)totalDamage / 12 + 0.01;
|
||||
|
||||
Priority = TimerPriority.TwentyFiveMS;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (!m_Defender.Alive || m_DamageRemaining <= 0)
|
||||
{
|
||||
Stop();
|
||||
Registry.Remove(m_Defender);
|
||||
return;
|
||||
}
|
||||
|
||||
m_DamageRemaining -= DamagePerTick;
|
||||
m_DamageToDo += DamagePerTick;
|
||||
|
||||
if (m_DamageRemaining <= 0 && m_DamageToDo < 1)
|
||||
m_DamageToDo = 1.0; //Confirm this 'round up' at the end
|
||||
|
||||
int damage = (int)m_DamageToDo;
|
||||
|
||||
if (damage > 0)
|
||||
{
|
||||
m_Defender.Damage(damage, m_Attacker);
|
||||
m_DamageToDo -= damage;
|
||||
}
|
||||
|
||||
if (!m_Defender.Alive || m_DamageRemaining <= 0)
|
||||
{
|
||||
Stop();
|
||||
Registry.Remove(m_Defender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Projects/Scripts/Items/Weapons/Abilities/InfectiousStrike.cs
Normal file
78
Projects/Scripts/Items/Weapons/Abilities/InfectiousStrike.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// This special move represents a significant change to the use of poisons in Age of Shadows.
|
||||
/// Now, only certain weapon types <20> those that have Infectious Strike as an available special move <20> will be able to be
|
||||
/// poisoned.
|
||||
/// Targets will no longer be poisoned at random when hit by poisoned weapons.
|
||||
/// Instead, the wielder must use this ability to deliver the venom.
|
||||
/// While no skill in Poisoning is directly required to use this ability, being knowledgeable in the application and use of
|
||||
/// toxins
|
||||
/// will allow a character to use Infectious Strike at reduced mana cost and with a chance to inflict more deadly poison on
|
||||
/// his victim.
|
||||
/// With this change, weapons will no longer be corroded by poison.
|
||||
/// Level 5 poison will be possible when using this special move.
|
||||
/// </summary>
|
||||
public class InfectiousStrike : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 15;
|
||||
|
||||
public override bool RequiresTactics(Mobile from)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
if (!(attacker.Weapon is BaseWeapon weapon))
|
||||
return;
|
||||
|
||||
Poison p = weapon.Poison;
|
||||
|
||||
if (p == null || weapon.PoisonCharges <= 0)
|
||||
{
|
||||
attacker.SendLocalizedMessage(
|
||||
1061141); // Your weapon must have a dose of poison to perform an infectious strike!
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
--weapon.PoisonCharges;
|
||||
|
||||
// Infectious strike special move now uses poisoning skill to help determine potency
|
||||
int maxLevel = attacker.Skills.Poisoning.Fixed / 200;
|
||||
if (maxLevel < 0) maxLevel = 0;
|
||||
if (p.Level > maxLevel) p = Poison.GetPoison(maxLevel);
|
||||
|
||||
if (attacker.Skills.Poisoning.Value / 100.0 > Utility.RandomDouble())
|
||||
{
|
||||
int level = p.Level + 1;
|
||||
Poison newPoison = Poison.GetPoison(level);
|
||||
|
||||
if (newPoison != null)
|
||||
{
|
||||
p = newPoison;
|
||||
|
||||
attacker.SendLocalizedMessage(1060080); // Your precise strike has increased the level of the poison by 1
|
||||
defender.SendLocalizedMessage(1060081); // The poison seems extra effective!
|
||||
}
|
||||
}
|
||||
|
||||
defender.PlaySound(0xDD);
|
||||
defender.FixedParticles(0x3728, 244, 25, 9941, 1266, 0, EffectLayer.Waist);
|
||||
|
||||
if (defender.ApplyPoison(attacker, p) != ApplyPoisonResult.Immune)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1008096, true, defender.Name); // You have poisoned your target :
|
||||
defender.SendLocalizedMessage(1008097, false, attacker.Name); // : poisoned you!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Projects/Scripts/Items/Weapons/Abilities/MortalStrike.cs
Normal file
81
Projects/Scripts/Items/Weapons/Abilities/MortalStrike.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// The assassin's friend.
|
||||
/// A successful Mortal Strike will render its victim unable to heal any damage for several seconds.
|
||||
/// Use a gruesome follow-up to finish off your foe.
|
||||
/// </summary>
|
||||
public class MortalStrike : WeaponAbility
|
||||
{
|
||||
public static readonly TimeSpan PlayerDuration = TimeSpan.FromSeconds(6.0);
|
||||
public static readonly TimeSpan NPCDuration = TimeSpan.FromSeconds(12.0);
|
||||
|
||||
private static Dictionary<Mobile, InternalTimer> m_Table = new Dictionary<Mobile, InternalTimer>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true)) return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060086); // You deliver a mortal wound!
|
||||
defender.SendLocalizedMessage(1060087); // You have been mortally wounded!
|
||||
|
||||
defender.PlaySound(0x1E1);
|
||||
defender.FixedParticles(0x37B9, 244, 25, 9944, 31, 0, EffectLayer.Waist);
|
||||
|
||||
// Do not reset timer if one is already in place.
|
||||
if (!IsWounded(defender))
|
||||
BeginWound(defender, defender.Player ? PlayerDuration : NPCDuration);
|
||||
}
|
||||
|
||||
public static bool IsWounded(Mobile m)
|
||||
{
|
||||
return m_Table.ContainsKey(m);
|
||||
}
|
||||
|
||||
public static void BeginWound(Mobile m, TimeSpan duration)
|
||||
{
|
||||
if (m_Table.TryGetValue(m, out InternalTimer timer))
|
||||
timer?.Stop();
|
||||
|
||||
m_Table[m] = timer = new InternalTimer(m, duration);
|
||||
timer.Start();
|
||||
|
||||
m.YellowHealthbar = true;
|
||||
}
|
||||
|
||||
public static void EndWound(Mobile m)
|
||||
{
|
||||
if (m_Table.TryGetValue(m, out InternalTimer timer))
|
||||
{
|
||||
timer.Stop();
|
||||
m_Table.Remove(m);
|
||||
}
|
||||
|
||||
m.YellowHealthbar = false;
|
||||
m.SendLocalizedMessage(1060208); // You are no longer mortally wounded.
|
||||
}
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
private Mobile m_Mobile;
|
||||
|
||||
public InternalTimer(Mobile m, TimeSpan duration) : base(duration)
|
||||
{
|
||||
m_Mobile = m;
|
||||
Priority = TimerPriority.TwoFiftyMS;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
EndWound(m_Mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Projects/Scripts/Items/Weapons/Abilities/MovingShot.cs
Normal file
37
Projects/Scripts/Items/Weapons/Abilities/MovingShot.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Available on some crossbows, this special move allows archers to fire while on the move.
|
||||
/// This shot is somewhat less accurate than normal, but the ability to fire while running is a clear advantage.
|
||||
/// </summary>
|
||||
public class MovingShot : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 15;
|
||||
public override int AccuracyBonus => -25;
|
||||
|
||||
public override bool ValidatesDuringHit => false;
|
||||
|
||||
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
|
||||
{
|
||||
return Validate(attacker) && CheckMana(attacker, true);
|
||||
}
|
||||
|
||||
public override void OnMiss(Mobile attacker, Mobile defender)
|
||||
{
|
||||
//Validates in OnSwing for accuracy scalar
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060089); // You fail to execute your special move
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
//Validates in OnSwing for accuracy scalar
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060216); // Your shot was successful
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Projects/Scripts/Items/Weapons/Abilities/NerveStrike.cs
Normal file
81
Projects/Scripts/Items/Weapons/Abilities/NerveStrike.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Does damage and paralyses your opponent for a short time.
|
||||
/// </summary>
|
||||
public class NerveStrike : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1070768,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
|
||||
{
|
||||
if (defender.Paralyzed)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1061923); // The target is already frozen.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
bool cantpara = Items.ParalyzingBlow.IsImmune(defender);
|
||||
|
||||
if (cantpara)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1070804); // Your target resists paralysis.
|
||||
defender.SendLocalizedMessage(1070813); // You resist paralysis.
|
||||
}
|
||||
else
|
||||
{
|
||||
attacker.SendLocalizedMessage(1063356); // You cripple your target with a nerve strike!
|
||||
defender.SendLocalizedMessage(1063357); // Your attacker dealt a crippling nerve strike!
|
||||
}
|
||||
|
||||
attacker.PlaySound(0x204);
|
||||
defender.FixedEffect(0x376A, 9, 32);
|
||||
defender.FixedParticles(0x37C4, 1, 8, 0x13AF, 0, 0, EffectLayer.Waist);
|
||||
|
||||
if (Core.ML)
|
||||
{
|
||||
AOS.Damage(defender, attacker,
|
||||
(int)(15.0 * (attacker.Skills.Bushido.Value - 50.0) / 70.0 + Utility.Random(10)), true, 100,
|
||||
0, 0, 0, 0); //0-25
|
||||
|
||||
if (!cantpara && (150.0 / 7.0 + 4.0 * attacker.Skills.Bushido.Value / 7.0) / 100.0 >
|
||||
Utility.RandomDouble())
|
||||
{
|
||||
defender.Paralyze(TimeSpan.FromSeconds(2.0));
|
||||
Items.ParalyzingBlow.BeginImmunity(defender, Items.ParalyzingBlow.FreezeDelayDuration);
|
||||
}
|
||||
}
|
||||
else if (!cantpara)
|
||||
{
|
||||
AOS.Damage(defender, attacker, (int)(15.0 * (attacker.Skills.Bushido.Value - 50.0) / 70.0 + 10),
|
||||
true, 100, 0, 0, 0, 0); //10-25
|
||||
defender.Freeze(TimeSpan.FromSeconds(2.0));
|
||||
Items.ParalyzingBlow.BeginImmunity(defender, Items.ParalyzingBlow.FreezeDelayDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
Projects/Scripts/Items/Weapons/Abilities/ParalyzingBlow.cs
Normal file
122
Projects/Scripts/Items/Weapons/Abilities/ParalyzingBlow.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// A successful Paralyzing Blow will leave the target stunned, unable to move, attack, or cast spells, for a few seconds.
|
||||
/// </summary>
|
||||
public class ParalyzingBlow : WeaponAbility
|
||||
{
|
||||
public static readonly TimeSpan PlayerFreezeDuration = TimeSpan.FromSeconds(3.0);
|
||||
public static readonly TimeSpan NPCFreezeDuration = TimeSpan.FromSeconds(6.0);
|
||||
|
||||
public static readonly TimeSpan FreezeDelayDuration = TimeSpan.FromSeconds(8.0);
|
||||
|
||||
private static Dictionary<Mobile, InternalTimer> m_Table = new Dictionary<Mobile, InternalTimer>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
|
||||
// No longer active in pub21:
|
||||
/*public override bool CheckSkills( Mobile from )
|
||||
{
|
||||
if ( !base.CheckSkills( from ) )
|
||||
return false;
|
||||
|
||||
if ( !(from.Weapon is Fists) )
|
||||
return true;
|
||||
|
||||
Skill skill = from.Skills.Anatomy;
|
||||
|
||||
if ( skill?.Base >= 80.0 )
|
||||
return true;
|
||||
|
||||
from.SendLocalizedMessage( 1061811 ); // You lack the required anatomy skill to perform that attack!
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
public override bool RequiresTactics(Mobile from)
|
||||
{
|
||||
return !(from.Weapon is BaseWeapon weapon && weapon.Skill == SkillName.Wrestling);
|
||||
}
|
||||
|
||||
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
|
||||
{
|
||||
if (defender.Paralyzed)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1061923); // The target is already frozen.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
if (IsImmune(defender)) //Intentionally going after Mana consumption
|
||||
{
|
||||
attacker.SendLocalizedMessage(1070804); // Your target resists paralysis.
|
||||
defender.SendLocalizedMessage(1070813); // You resist paralysis.
|
||||
return;
|
||||
}
|
||||
|
||||
defender.FixedEffect(0x376A, 9, 32);
|
||||
defender.PlaySound(0x204);
|
||||
|
||||
attacker.SendLocalizedMessage(1060163); // You deliver a paralyzing blow!
|
||||
defender.SendLocalizedMessage(1060164); // The attack has temporarily paralyzed you!
|
||||
|
||||
TimeSpan duration = defender.Player ? PlayerFreezeDuration : NPCFreezeDuration;
|
||||
|
||||
// Treat it as paralyze not as freeze, effect must be removed when damaged.
|
||||
defender.Paralyze(duration);
|
||||
|
||||
BeginImmunity(defender, duration + FreezeDelayDuration);
|
||||
}
|
||||
|
||||
public static bool IsImmune(Mobile m)
|
||||
{
|
||||
return m_Table.ContainsKey(m);
|
||||
}
|
||||
|
||||
public static void BeginImmunity(Mobile m, TimeSpan duration)
|
||||
{
|
||||
if (m_Table.TryGetValue(m, out InternalTimer timer))
|
||||
timer?.Stop();
|
||||
|
||||
m_Table[m] = timer = new InternalTimer(m, duration);
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public static void EndImmunity(Mobile m)
|
||||
{
|
||||
if (m_Table.TryGetValue(m, out InternalTimer timer))
|
||||
{
|
||||
timer?.Stop();
|
||||
m_Table.Remove(m);
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
private Mobile m_Mobile;
|
||||
|
||||
public InternalTimer(Mobile m, TimeSpan duration) : base(duration)
|
||||
{
|
||||
m_Mobile = m;
|
||||
Priority = TimerPriority.TwoFiftyMS;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
EndImmunity(m_Mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Projects/Scripts/Items/Weapons/Abilities/RidingSwipe.cs
Normal file
77
Projects/Scripts/Items/Weapons/Abilities/RidingSwipe.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// If you are on foot, dismounts your opponent and damage the ethereal's rider or the
|
||||
/// living mount(which must be healed before ridden again). If you are mounted, damages
|
||||
/// and stuns the mounted opponent.
|
||||
/// </summary>
|
||||
public class RidingSwipe : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 30;
|
||||
|
||||
public override bool RequiresSE => true;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Bushido) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1070768,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!defender.Mounted)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1060848); // This attack only works on mounted targets
|
||||
ClearCurrentAbility(attacker);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
if (!attacker.Mounted)
|
||||
{
|
||||
Mobile mount = defender.Mount as Mobile;
|
||||
BaseMount.Dismount(defender);
|
||||
|
||||
if (mount != null) //Ethy mounts don't take damage
|
||||
{
|
||||
int amount = 10 + (int)(10.0 * (attacker.Skills.Bushido.Value - 50.0) / 70.0 + 5);
|
||||
|
||||
AOS.Damage(mount, null, amount, 100, 0, 0, 0,
|
||||
0); //The mount just takes damage, there's no flagging as if it was attacking the mount directly
|
||||
|
||||
//TODO: Mount prevention until mount healed
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int amount = 10 + (int)(10.0 * (attacker.Skills.Bushido.Value - 50.0) / 70.0 + 5);
|
||||
|
||||
AOS.Damage(defender, attacker, amount, 100, 0, 0, 0, 0);
|
||||
|
||||
if (Items.ParalyzingBlow.IsImmune(defender)) //Does it still do damage?
|
||||
{
|
||||
attacker.SendLocalizedMessage(1070804); // Your target resists paralysis.
|
||||
defender.SendLocalizedMessage(1070813); // You resist paralysis.
|
||||
}
|
||||
else
|
||||
{
|
||||
defender.Paralyze(TimeSpan.FromSeconds(3.0));
|
||||
Items.ParalyzingBlow.BeginImmunity(defender, Items.ParalyzingBlow.FreezeDelayDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Projects/Scripts/Items/Weapons/Abilities/ShadowStrike.cs
Normal file
54
Projects/Scripts/Items/Weapons/Abilities/ShadowStrike.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// This powerful ability requires secondary skills to activate.
|
||||
/// Successful use of Shadowstrike deals extra damage to the target <20> and renders the attacker invisible!
|
||||
/// Only those who are adept at the art of stealth will be able to use this ability.
|
||||
/// </summary>
|
||||
public class ShadowStrike : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 20;
|
||||
public override double DamageScalar => 1.25;
|
||||
|
||||
public override bool RequiresTactics(Mobile from)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (!base.CheckSkills(from))
|
||||
return false;
|
||||
|
||||
Skill skill = from.Skills.Stealth;
|
||||
|
||||
if (skill?.Value >= 80.0)
|
||||
return true;
|
||||
|
||||
from.SendLocalizedMessage(1060183); // You lack the required stealth to perform that attack
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1060078); // You strike and hide in the shadows!
|
||||
defender.SendLocalizedMessage(1060079); // You are dazed by the attack and your attacker vanishes!
|
||||
|
||||
Effects.SendLocationParticles(EffectItem.Create(attacker.Location, attacker.Map, EffectItem.DefaultDuration),
|
||||
0x376A, 8, 12, 9943);
|
||||
attacker.PlaySound(0x482);
|
||||
|
||||
defender.FixedEffect(0x37BE, 20, 25);
|
||||
|
||||
attacker.Combatant = null;
|
||||
attacker.Warmode = false;
|
||||
attacker.Hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Projects/Scripts/Items/Weapons/Abilities/TalonStrike.cs
Normal file
98
Projects/Scripts/Items/Weapons/Abilities/TalonStrike.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Attack with increased damage with additional damage over time.
|
||||
/// </summary>
|
||||
public class TalonStrike : WeaponAbility
|
||||
{
|
||||
private static HashSet<Mobile> m_Table = new HashSet<Mobile>();
|
||||
|
||||
public override int BaseMana => 30;
|
||||
public override double DamageScalar => 1.2;
|
||||
|
||||
public override bool CheckSkills(Mobile from)
|
||||
{
|
||||
if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
|
||||
{
|
||||
from.SendLocalizedMessage(1063352,
|
||||
"50"); // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CheckSkills(from);
|
||||
}
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (m_Table.Contains(defender) || !Validate(attacker) || !CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
attacker.SendLocalizedMessage(1063358); // You deliver a talon strike!
|
||||
defender.SendLocalizedMessage(1063359); // Your attacker delivers a talon strike!
|
||||
|
||||
defender.FixedParticles(0x373A, 1, 17, 0x26BC, 0x662, 0, EffectLayer.Waist);
|
||||
|
||||
InternalTimer timer = new InternalTimer(defender,
|
||||
(int)(10.0 * (attacker.Skills.Ninjitsu.Value - 50.0) / 70.0 + 5)); //5 - 15 damage
|
||||
|
||||
timer.Start();
|
||||
|
||||
m_Table.Add(defender);
|
||||
}
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
private readonly double DamagePerTick;
|
||||
private double m_DamageRemaining;
|
||||
private double m_DamageToDo;
|
||||
private Mobile m_Defender;
|
||||
|
||||
public InternalTimer(Mobile defender, int totalDamage)
|
||||
: base(TimeSpan.Zero, TimeSpan.FromSeconds(0.25),
|
||||
12) // 3 seconds at .25 seconds apart = 12. Confirm delay inbetween of .25 each.
|
||||
{
|
||||
m_Defender = defender;
|
||||
m_DamageRemaining = totalDamage;
|
||||
Priority = TimerPriority.TwentyFiveMS;
|
||||
|
||||
DamagePerTick = (double)totalDamage / 12 + .01;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (!m_Defender.Alive || m_DamageRemaining <= 0)
|
||||
{
|
||||
Stop();
|
||||
m_Table.Remove(m_Defender);
|
||||
return;
|
||||
}
|
||||
|
||||
m_DamageRemaining -= DamagePerTick;
|
||||
m_DamageToDo += DamagePerTick;
|
||||
|
||||
if (m_DamageRemaining <= 0 && m_DamageToDo < 1)
|
||||
m_DamageToDo = 1.0; //Confirm this 'round up' at the end
|
||||
|
||||
int damage = (int)m_DamageToDo;
|
||||
|
||||
if (damage > 0)
|
||||
{
|
||||
//m_Defender.Damage( damage, m_Attacker, false );
|
||||
m_Defender.Hits -= damage; //Don't show damage, don't disrupt
|
||||
m_DamageToDo -= damage;
|
||||
}
|
||||
|
||||
if (!m_Defender.Alive || m_DamageRemaining <= 0)
|
||||
{
|
||||
Stop();
|
||||
m_Table.Remove(m_Defender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
470
Projects/Scripts/Items/Weapons/Abilities/WeaponAbility.cs
Normal file
470
Projects/Scripts/Items/Weapons/Abilities/WeaponAbility.cs
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Engines.ConPVP;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
using Server.Spells;
|
||||
using Server.Spells.Bushido;
|
||||
using Server.Spells.Necromancy;
|
||||
using Server.Spells.Ninjitsu;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
public abstract class WeaponAbility
|
||||
{
|
||||
public static WeaponAbility[] Abilities{ get; } = {
|
||||
null,
|
||||
new ArmorIgnore(),
|
||||
new BleedAttack(),
|
||||
new ConcussionBlow(),
|
||||
new CrushingBlow(),
|
||||
new Disarm(),
|
||||
new Dismount(),
|
||||
new DoubleStrike(),
|
||||
new InfectiousStrike(),
|
||||
new MortalStrike(),
|
||||
new MovingShot(),
|
||||
new ParalyzingBlow(),
|
||||
new ShadowStrike(),
|
||||
new WhirlwindAttack(),
|
||||
|
||||
new RidingSwipe(),
|
||||
new FrenziedWhirlwind(),
|
||||
new Block(),
|
||||
new DefenseMastery(),
|
||||
new NerveStrike(),
|
||||
new TalonStrike(),
|
||||
new Feint(),
|
||||
new DualWield(),
|
||||
new DoubleShot(),
|
||||
new ArmorPierce(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new Disrobe()
|
||||
};
|
||||
|
||||
public static readonly WeaponAbility ArmorIgnore = Abilities[1];
|
||||
public static readonly WeaponAbility BleedAttack = Abilities[2];
|
||||
public static readonly WeaponAbility ConcussionBlow = Abilities[3];
|
||||
public static readonly WeaponAbility CrushingBlow = Abilities[4];
|
||||
public static readonly WeaponAbility Disarm = Abilities[5];
|
||||
public static readonly WeaponAbility Dismount = Abilities[6];
|
||||
public static readonly WeaponAbility DoubleStrike = Abilities[7];
|
||||
public static readonly WeaponAbility InfectiousStrike = Abilities[8];
|
||||
public static readonly WeaponAbility MortalStrike = Abilities[9];
|
||||
public static readonly WeaponAbility MovingShot = Abilities[10];
|
||||
public static readonly WeaponAbility ParalyzingBlow = Abilities[11];
|
||||
public static readonly WeaponAbility ShadowStrike = Abilities[12];
|
||||
public static readonly WeaponAbility WhirlwindAttack = Abilities[13];
|
||||
|
||||
public static readonly WeaponAbility RidingSwipe = Abilities[14];
|
||||
public static readonly WeaponAbility FrenziedWhirlwind = Abilities[15];
|
||||
public static readonly WeaponAbility Block = Abilities[16];
|
||||
public static readonly WeaponAbility DefenseMastery = Abilities[17];
|
||||
public static readonly WeaponAbility NerveStrike = Abilities[18];
|
||||
public static readonly WeaponAbility TalonStrike = Abilities[19];
|
||||
public static readonly WeaponAbility Feint = Abilities[20];
|
||||
public static readonly WeaponAbility DualWield = Abilities[21];
|
||||
public static readonly WeaponAbility DoubleShot = Abilities[22];
|
||||
public static readonly WeaponAbility ArmorPierce = Abilities[23];
|
||||
|
||||
public static readonly WeaponAbility Bladeweave = Abilities[24];
|
||||
public static readonly WeaponAbility ForceArrow = Abilities[25];
|
||||
public static readonly WeaponAbility LightningArrow = Abilities[26];
|
||||
public static readonly WeaponAbility PsychicAttack = Abilities[27];
|
||||
public static readonly WeaponAbility SerpentArrow = Abilities[28];
|
||||
public static readonly WeaponAbility ForceOfNature = Abilities[29];
|
||||
|
||||
public static readonly WeaponAbility Disrobe = Abilities[30];
|
||||
|
||||
private static Dictionary<Mobile, WeaponAbilityContext> m_PlayersTable = new Dictionary<Mobile, WeaponAbilityContext>();
|
||||
|
||||
public virtual int BaseMana => 0;
|
||||
|
||||
public virtual int AccuracyBonus => 0;
|
||||
public virtual double DamageScalar => 1.0;
|
||||
|
||||
public virtual bool RequiresSE => false;
|
||||
|
||||
public static Dictionary<Mobile, WeaponAbility> Table{ get; } = new Dictionary<Mobile, WeaponAbility>();
|
||||
|
||||
public virtual bool ValidatesDuringHit => true;
|
||||
|
||||
public virtual void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnMiss(Mobile attacker, Mobile defender)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual bool OnBeforeSwing(Mobile attacker, Mobile defender)
|
||||
{
|
||||
// Here because you must be sure you can use the skill before calling CheckHit if the ability has a HCI bonus for example
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool OnBeforeDamage(Mobile attacker, Mobile defender)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool RequiresTactics(Mobile from)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual double GetRequiredSkill(Mobile from)
|
||||
{
|
||||
if (from.Weapon is BaseWeapon weapon)
|
||||
{
|
||||
if (weapon.PrimaryAbility == this)
|
||||
return 70.0;
|
||||
if (weapon.SecondaryAbility == this)
|
||||
return 90.0;
|
||||
}
|
||||
|
||||
return 200.0;
|
||||
}
|
||||
|
||||
public virtual int CalculateMana(Mobile from)
|
||||
{
|
||||
int mana = BaseMana;
|
||||
|
||||
double skillTotal = GetSkill(from, SkillName.Swords) + GetSkill(from, SkillName.Macing)
|
||||
+ GetSkill(from, SkillName.Fencing) +
|
||||
GetSkill(from, SkillName.Archery) +
|
||||
GetSkill(from, SkillName.Parry)
|
||||
+ GetSkill(from, SkillName.Lumberjacking) +
|
||||
GetSkill(from, SkillName.Stealth)
|
||||
+ GetSkill(from, SkillName.Poisoning) +
|
||||
GetSkill(from, SkillName.Bushido) +
|
||||
GetSkill(from, SkillName.Ninjitsu);
|
||||
|
||||
if (skillTotal >= 300.0)
|
||||
mana -= 10;
|
||||
else if (skillTotal >= 200.0)
|
||||
mana -= 5;
|
||||
|
||||
double scalar = 1.0;
|
||||
if (!MindRotSpell.GetMindRotScalar(from, ref scalar))
|
||||
scalar = 1.0;
|
||||
|
||||
// Lower Mana Cost = 40%
|
||||
int lmc = Math.Min(AosAttributes.GetValue(from, AosAttribute.LowerManaCost), 40);
|
||||
|
||||
scalar -= (double)lmc / 100;
|
||||
mana = (int)(mana * scalar);
|
||||
|
||||
// Using a special move within 3 seconds of the previous special move costs double mana
|
||||
if (GetContext(from) != null)
|
||||
mana *= 2;
|
||||
|
||||
return mana;
|
||||
}
|
||||
|
||||
public virtual bool CheckWeaponSkill(Mobile from)
|
||||
{
|
||||
if (!(from.Weapon is BaseWeapon weapon))
|
||||
return false;
|
||||
|
||||
Skill skill = from.Skills[weapon.Skill];
|
||||
double reqSkill = GetRequiredSkill(from);
|
||||
bool reqTactics = Core.ML && RequiresTactics(from);
|
||||
|
||||
if (Core.ML && reqTactics && from.Skills.Tactics.Base < reqSkill)
|
||||
{
|
||||
from.SendLocalizedMessage(1079308,
|
||||
reqSkill.ToString()); // You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skill?.Base >= reqSkill)
|
||||
return true;
|
||||
|
||||
/* <UBWS> */
|
||||
if (weapon.WeaponAttributes.UseBestSkill > 0 && (from.Skills.Swords.Base >= reqSkill ||
|
||||
from.Skills.Macing.Base >= reqSkill ||
|
||||
from.Skills.Fencing.Base >= reqSkill))
|
||||
return true;
|
||||
/* </UBWS> */
|
||||
|
||||
if (reqTactics)
|
||||
from.SendLocalizedMessage(1079308,
|
||||
reqSkill.ToString()); // You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
|
||||
else
|
||||
from.SendLocalizedMessage(1060182,
|
||||
reqSkill.ToString()); // You need ~1_SKILL_REQUIREMENT~ weapon skill to perform that attack
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool CheckSkills(Mobile from)
|
||||
{
|
||||
return CheckWeaponSkill(from);
|
||||
}
|
||||
|
||||
public virtual double GetSkill(Mobile from, SkillName skillName) => from.Skills[skillName]?.Value ?? 0.0;
|
||||
|
||||
public virtual bool CheckMana(Mobile from, bool consume)
|
||||
{
|
||||
int mana = CalculateMana(from);
|
||||
|
||||
if (from.Mana < mana)
|
||||
{
|
||||
if (from is BaseCreature creature && creature.HasManaOveride) return true;
|
||||
|
||||
from.SendLocalizedMessage(1060181,
|
||||
mana.ToString()); // You need ~1_MANA_REQUIREMENT~ mana to perform that attack
|
||||
return false;
|
||||
}
|
||||
|
||||
if (consume)
|
||||
{
|
||||
if (GetContext(from) == null)
|
||||
{
|
||||
Timer timer = new WeaponAbilityTimer(from);
|
||||
timer.Start();
|
||||
|
||||
AddContext(from, new WeaponAbilityContext(timer));
|
||||
}
|
||||
|
||||
from.Mana -= mana;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool Validate(Mobile from)
|
||||
{
|
||||
if (!from.Player)
|
||||
return true;
|
||||
|
||||
NetState state = from.NetState;
|
||||
|
||||
if (state == null)
|
||||
return false;
|
||||
|
||||
if (RequiresSE && !state.SupportsExpansion(Expansion.SE))
|
||||
{
|
||||
from.SendLocalizedMessage(1063456); // You must upgrade to Samurai Empire in order to use that ability.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HonorableExecution.IsUnderPenalty(from) || AnimalForm.UnderTransformation(from))
|
||||
{
|
||||
from.SendLocalizedMessage(1063024); // You cannot perform this special move right now.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Core.ML && from.Spell != null)
|
||||
{
|
||||
from.SendLocalizedMessage(1063024); // You cannot perform this special move right now.
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Dueling
|
||||
|
||||
string option = null;
|
||||
|
||||
if (this is ArmorIgnore)
|
||||
option = "Armor Ignore";
|
||||
else if (this is BleedAttack)
|
||||
option = "Bleed Attack";
|
||||
else if (this is ConcussionBlow)
|
||||
option = "Concussion Blow";
|
||||
else if (this is CrushingBlow)
|
||||
option = "Crushing Blow";
|
||||
else if (this is Disarm)
|
||||
option = "Disarm";
|
||||
else if (this is Dismount)
|
||||
option = "Dismount";
|
||||
else if (this is DoubleStrike)
|
||||
option = "Double Strike";
|
||||
else if (this is InfectiousStrike)
|
||||
option = "Infectious Strike";
|
||||
else if (this is MortalStrike)
|
||||
option = "Mortal Strike";
|
||||
else if (this is MovingShot)
|
||||
option = "Moving Shot";
|
||||
else if (this is ParalyzingBlow)
|
||||
option = "Paralyzing Blow";
|
||||
else if (this is ShadowStrike)
|
||||
option = "Shadow Strike";
|
||||
else if (this is WhirlwindAttack)
|
||||
option = "Whirlwind Attack";
|
||||
else if (this is RidingSwipe)
|
||||
option = "Riding Swipe";
|
||||
else if (this is FrenziedWhirlwind)
|
||||
option = "Frenzied Whirlwind";
|
||||
else if (this is Block)
|
||||
option = "Block";
|
||||
else if (this is DefenseMastery)
|
||||
option = "Defense Mastery";
|
||||
else if (this is NerveStrike)
|
||||
option = "Nerve Strike";
|
||||
else if (this is TalonStrike)
|
||||
option = "Talon Strike";
|
||||
else if (this is Feint)
|
||||
option = "Feint";
|
||||
else if (this is DualWield)
|
||||
option = "Dual Wield";
|
||||
else if (this is DoubleShot)
|
||||
option = "Double Shot";
|
||||
else if (this is ArmorPierce)
|
||||
option = "Armor Pierce";
|
||||
|
||||
|
||||
if (option != null && !DuelContext.AllowSpecialAbility(from, option, true))
|
||||
return false;
|
||||
|
||||
#endregion
|
||||
|
||||
return CheckSkills(from) && CheckMana(from, false);
|
||||
}
|
||||
|
||||
public static bool IsWeaponAbility(Mobile m, WeaponAbility a)
|
||||
{
|
||||
return a == null || !m.Player || m.Weapon is BaseWeapon weapon &&
|
||||
(weapon.PrimaryAbility == a || weapon.SecondaryAbility == a);
|
||||
}
|
||||
|
||||
public static WeaponAbility GetCurrentAbility(Mobile m)
|
||||
{
|
||||
if (!Core.AOS)
|
||||
{
|
||||
ClearCurrentAbility(m);
|
||||
return null;
|
||||
}
|
||||
|
||||
Table.TryGetValue(m, out WeaponAbility a);
|
||||
|
||||
if (!IsWeaponAbility(m, a))
|
||||
{
|
||||
ClearCurrentAbility(m);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (a?.ValidatesDuringHit == true && !a.Validate(m))
|
||||
{
|
||||
ClearCurrentAbility(m);
|
||||
return null;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
public static bool SetCurrentAbility(Mobile m, WeaponAbility a)
|
||||
{
|
||||
if (!Core.AOS)
|
||||
{
|
||||
ClearCurrentAbility(m);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsWeaponAbility(m, a))
|
||||
{
|
||||
ClearCurrentAbility(m);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a?.Validate(m) == false)
|
||||
{
|
||||
ClearCurrentAbility(m);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a == null)
|
||||
{
|
||||
Table.Remove(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
SpecialMove.ClearCurrentMove(m);
|
||||
Table[m] = a;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ClearCurrentAbility(Mobile m)
|
||||
{
|
||||
Table.Remove(m);
|
||||
|
||||
if (Core.AOS && m.NetState != null)
|
||||
m.Send(ClearWeaponAbility.Instance);
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.SetAbility += EventSink_SetAbility;
|
||||
}
|
||||
|
||||
private static void EventSink_SetAbility(SetAbilityEventArgs e)
|
||||
{
|
||||
int index = e.Index;
|
||||
|
||||
if (index == 0)
|
||||
ClearCurrentAbility(e.Mobile);
|
||||
else if (index >= 1 && index < Abilities.Length)
|
||||
SetCurrentAbility(e.Mobile, Abilities[index]);
|
||||
}
|
||||
|
||||
private static void AddContext(Mobile m, WeaponAbilityContext context)
|
||||
{
|
||||
m_PlayersTable[m] = context;
|
||||
}
|
||||
|
||||
private static void RemoveContext(Mobile m)
|
||||
{
|
||||
WeaponAbilityContext context = GetContext(m);
|
||||
|
||||
if (context != null)
|
||||
RemoveContext(m, context);
|
||||
}
|
||||
|
||||
private static void RemoveContext(Mobile m, WeaponAbilityContext context)
|
||||
{
|
||||
m_PlayersTable.Remove(m);
|
||||
|
||||
context.Timer.Stop();
|
||||
}
|
||||
|
||||
private static WeaponAbilityContext GetContext(Mobile m)
|
||||
{
|
||||
m_PlayersTable.TryGetValue(m, out WeaponAbilityContext context);
|
||||
return context;
|
||||
}
|
||||
|
||||
private class WeaponAbilityTimer : Timer
|
||||
{
|
||||
private Mobile m_Mobile;
|
||||
|
||||
public WeaponAbilityTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0))
|
||||
{
|
||||
m_Mobile = from;
|
||||
|
||||
Priority = TimerPriority.TwentyFiveMS;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
RemoveContext(m_Mobile);
|
||||
}
|
||||
}
|
||||
|
||||
private class WeaponAbilityContext
|
||||
{
|
||||
public WeaponAbilityContext(Timer timer)
|
||||
{
|
||||
Timer = timer;
|
||||
}
|
||||
|
||||
public Timer Timer{ get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Projects/Scripts/Items/Weapons/Abilities/WhirlwindAttack.cs
Normal file
64
Projects/Scripts/Items/Weapons/Abilities/WhirlwindAttack.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Server.Spells;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// A godsend to a warrior surrounded, the Whirlwind Attack allows the fighter to strike at all nearby targets in one mighty
|
||||
/// spinning swing.
|
||||
/// </summary>
|
||||
public class WhirlwindAttack : WeaponAbility
|
||||
{
|
||||
public override int BaseMana => 15;
|
||||
|
||||
public override void OnHit(Mobile attacker, Mobile defender, int damage)
|
||||
{
|
||||
if (!Validate(attacker))
|
||||
return;
|
||||
|
||||
ClearCurrentAbility(attacker);
|
||||
|
||||
Map map = attacker.Map;
|
||||
|
||||
if (map == null)
|
||||
return;
|
||||
|
||||
if (!(attacker.Weapon is BaseWeapon weapon))
|
||||
return;
|
||||
|
||||
if (!CheckMana(attacker, true))
|
||||
return;
|
||||
|
||||
attacker.FixedEffect(0x3728, 10, 15);
|
||||
attacker.PlaySound(0x2A1);
|
||||
|
||||
List<Mobile> targets = attacker.GetMobilesInRange(1).Where(m =>
|
||||
m?.Deleted == false && m != defender && m != attacker && SpellHelper.ValidIndirectTarget(attacker, m) &&
|
||||
m.Map == attacker.Map && m.Alive && attacker.CanSee(m) && attacker.CanBeHarmful(m) &&
|
||||
attacker.InRange(m, weapon.MaxRange) && attacker.InLOS(m)).ToList();
|
||||
|
||||
if (targets.Count <= 0)
|
||||
return;
|
||||
|
||||
double bushido = attacker.Skills.Bushido.Value;
|
||||
double damageBonus = 1.0 + Math.Pow(targets.Count * bushido / 60, 2) / 100;
|
||||
|
||||
if (damageBonus > 2.0)
|
||||
damageBonus = 2.0;
|
||||
|
||||
attacker.RevealingAction();
|
||||
|
||||
for (int i = 0; i < targets.Count; ++i)
|
||||
{
|
||||
Mobile m = targets[i];
|
||||
|
||||
attacker.SendLocalizedMessage(1060161); // The whirling attack strikes a target!
|
||||
m.SendLocalizedMessage(1060162); // You are struck by the whirling attack and take damage!
|
||||
|
||||
weapon.OnHit(attacker, m, damageBonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue