v0.0.1-Alpha (#42)

This commit is contained in:
Kamron Batman 2019-08-02 18:16:11 -07:00 • committed by GitHub
parent d30f93c454
commit a36796c2c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3552 changed files with 2476 additions and 15223 deletions

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}
}

View 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!
}
}
}
}

View 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);
}
}
}
}

View 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
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}
}

View 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; }
}
}
}

View 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);
}
}
}
}