Formats UO Content (#201)

This commit is contained in:
Kamron Batman 2020-08-27 18:30:38 -07:00 • committed by GitHub
parent 86b7b3aed1
commit ad3775c4d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3249 changed files with 431083 additions and 440981 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,69 +3,69 @@ using Server.SkillHandlers;
namespace Server.Spells.Ninjitsu
{
public class Backstab : NinjaMove
{
public override int BaseMana => 30;
public override double RequiredSkill => Core.ML ? 40.0 : 20.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063089); // You prepare to Backstab your opponent.
public override bool ValidatesDuringHit => false;
public override double GetDamageScalar(Mobile attacker, Mobile defender)
public class Backstab : NinjaMove
{
double ninjitsu = attacker.Skills.Ninjitsu.Value;
public override int BaseMana => 30;
public override double RequiredSkill => Core.ML ? 40.0 : 20.0;
return 1.0 + ninjitsu / 360 + Tracking.GetStalkingBonus(attacker, defender) / 100;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063089); // You prepare to Backstab your opponent.
public override bool ValidatesDuringHit => false;
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
var ninjitsu = attacker.Skills.Ninjitsu.Value;
return 1.0 + ninjitsu / 360 + Tracking.GetStalkingBonus(attacker, defender) / 100;
}
public override bool Validate(Mobile from)
{
if (!from.Hidden || from.AllowedStealthSteps <= 0)
{
from.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.Validate(from);
}
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
var valid = Validate(attacker) && CheckMana(attacker, true);
if (valid)
{
attacker.BeginAction<Stealth>();
Timer.DelayCall(TimeSpan.FromSeconds(5.0), attacker.EndAction<Stealth>);
}
return valid;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
// Validates before swing
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063090); // You quickly stab your opponent as you come out of hiding!
defender.FixedParticles(0x37B9, 1, 5, 0x251D, 0x651, 0, EffectLayer.Waist);
attacker.RevealingAction();
CheckGain(attacker);
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063161); // You failed to properly use the element of surprise.
attacker.RevealingAction();
}
}
public override bool Validate(Mobile from)
{
if (!from.Hidden || from.AllowedStealthSteps <= 0)
{
from.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.Validate(from);
}
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
bool valid = Validate(attacker) && CheckMana(attacker, true);
if (valid)
{
attacker.BeginAction<Stealth>();
Timer.DelayCall(TimeSpan.FromSeconds(5.0), attacker.EndAction<Stealth>);
}
return valid;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
// Validates before swing
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063090); // You quickly stab your opponent as you come out of hiding!
defender.FixedParticles(0x37B9, 1, 5, 0x251D, 0x651, 0, EffectLayer.Waist);
attacker.RevealingAction();
CheckGain(attacker);
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063161); // You failed to properly use the element of surprise.
attacker.RevealingAction();
}
}
}

View file

@ -5,145 +5,159 @@ using Server.SkillHandlers;
namespace Server.Spells.Ninjitsu
{
public class DeathStrike : NinjaMove
{
private static readonly Dictionary<Mobile, DeathStrikeInfo> m_Table = new Dictionary<Mobile, DeathStrikeInfo>();
public override int BaseMana => 30;
public override double RequiredSkill => 85.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063091); // You prepare to hit your opponent with a Death Strike.
public override double GetDamageScalar(Mobile attacker, Mobile defender) => 0.5;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
public class DeathStrike : NinjaMove
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
private static readonly Dictionary<Mobile, DeathStrikeInfo> m_Table = new Dictionary<Mobile, DeathStrikeInfo>();
ClearCurrentMove(attacker);
public override int BaseMana => 30;
public override double RequiredSkill => 85.0;
double ninjitsu = attacker.Skills.Ninjitsu.Value;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063091); // You prepare to hit your opponent with a Death Strike.
double chance;
public override double GetDamageScalar(Mobile attacker, Mobile defender) => 0.5;
// TODO: should be defined onHit method, what if the player hit and remove the weapon before process? ;)
bool isRanged = attacker.Weapon is BaseRanged;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
if (ninjitsu < 100) // This formula is an approximation from OSI data. TODO: find correct formula
chance = 30 + (ninjitsu - 85) * 2.2;
else
chance = 63 + (ninjitsu - 100) * 1.1;
ClearCurrentMove(attacker);
if (chance / 100 < Utility.RandomDouble())
{
attacker.SendLocalizedMessage(1070779); // You missed your opponent with a Death Strike.
return;
}
var ninjitsu = attacker.Skills.Ninjitsu.Value;
int damageBonus = 0;
double chance;
if (m_Table.TryGetValue(defender, out DeathStrikeInfo info))
{
defender.SendLocalizedMessage(1063092); // Your opponent lands another Death Strike!
// TODO: should be defined onHit method, what if the player hit and remove the weapon before process? ;)
var isRanged = attacker.Weapon is BaseRanged;
if (info.m_Steps > 0)
damageBonus = attacker.Skills.Ninjitsu.Fixed / 150;
if (ninjitsu < 100) // This formula is an approximation from OSI data. TODO: find correct formula
chance = 30 + (ninjitsu - 85) * 2.2;
else
chance = 63 + (ninjitsu - 100) * 1.1;
info.m_Timer?.Stop();
if (chance / 100 < Utility.RandomDouble())
{
attacker.SendLocalizedMessage(1070779); // You missed your opponent with a Death Strike.
return;
}
m_Table.Remove(defender);
}
else
{
defender.SendLocalizedMessage(1063093); // You have been hit by a Death Strike! Move with caution!
}
var damageBonus = 0;
attacker.SendLocalizedMessage(1063094); // You inflict a Death Strike upon your opponent!
if (m_Table.TryGetValue(defender, out var info))
{
defender.SendLocalizedMessage(1063092); // Your opponent lands another Death Strike!
defender.FixedParticles(0x374A, 1, 17, 0x26BC, EffectLayer.Waist);
attacker.PlaySound(attacker.Female ? 0x50D : 0x50E);
if (info.m_Steps > 0)
damageBonus = attacker.Skills.Ninjitsu.Fixed / 150;
info = new DeathStrikeInfo(defender, attacker, damageBonus, isRanged)
{
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(5.0), ProcessDeathStrike, defender)
};
info.m_Timer?.Stop();
m_Table[defender] = info;
m_Table.Remove(defender);
}
else
{
defender.SendLocalizedMessage(1063093); // You have been hit by a Death Strike! Move with caution!
}
CheckGain(attacker);
attacker.SendLocalizedMessage(1063094); // You inflict a Death Strike upon your opponent!
defender.FixedParticles(0x374A, 1, 17, 0x26BC, EffectLayer.Waist);
attacker.PlaySound(attacker.Female ? 0x50D : 0x50E);
info = new DeathStrikeInfo(defender, attacker, damageBonus, isRanged)
{
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(5.0), ProcessDeathStrike, defender)
};
m_Table[defender] = info;
CheckGain(attacker);
}
public static void AddStep(Mobile m)
{
if (m_Table.TryGetValue(m, out var info) && ++info.m_Steps >= 5)
ProcessDeathStrike(m);
}
private static void ProcessDeathStrike(Mobile defender)
{
if (!m_Table.TryGetValue(defender, out var info))
return;
int damage;
var ninjitsu = info.m_Attacker.Skills.Ninjitsu.Value;
var stalkingBonus = Tracking.GetStalkingBonus(info.m_Attacker, info.m_Target);
if (Core.ML)
{
var scalar = (info.m_Attacker.Skills.Hiding.Value +
info.m_Attacker.Skills.Stealth.Value) / 220;
if (scalar > 1)
scalar = 1;
// New formula doesn't apply DamageBonus anymore, caps must be, directly, 60/30.
if (info.m_Steps >= 5)
damage = (int)Math.Floor(Math.Min(60, ninjitsu / 3 * (0.3 + 0.7 * scalar) + stalkingBonus));
else
damage = (int)Math.Floor(Math.Min(30, ninjitsu / 9 * (0.3 + 0.7 * scalar) + stalkingBonus));
if (info.m_isRanged)
damage /= 2;
}
else
{
var divisor = info.m_Steps >= 5 ? 30 : 80;
var baseDamage = ninjitsu / divisor * 10;
var maxDamage = info.m_Steps >= 5 ? 62 : 22;
damage = Math.Clamp((int)(baseDamage + stalkingBonus), 0, maxDamage) + info.m_DamageBonus;
}
if (Core.ML)
info.m_Target.Damage(damage, info.m_Attacker); // Damage is direct.
else
AOS.Damage(
info.m_Target,
info.m_Attacker,
damage,
true,
100,
0,
0,
0,
0,
0,
0,
false,
false,
true
); // Damage is physical.
info.m_Timer?.Stop();
m_Table.Remove(info.m_Target);
}
private class DeathStrikeInfo
{
public readonly Mobile m_Attacker;
public readonly int m_DamageBonus;
public readonly bool m_isRanged;
public readonly Mobile m_Target;
public int m_Steps;
public Timer m_Timer;
public DeathStrikeInfo(Mobile target, Mobile attacker, int damageBonus, bool isRanged)
{
m_Target = target;
m_Attacker = attacker;
m_DamageBonus = damageBonus;
m_isRanged = isRanged;
}
}
}
public static void AddStep(Mobile m)
{
if (m_Table.TryGetValue(m, out DeathStrikeInfo info) && ++info.m_Steps >= 5)
ProcessDeathStrike(m);
}
private static void ProcessDeathStrike(Mobile defender)
{
if (!m_Table.TryGetValue(defender, out DeathStrikeInfo info))
return;
int damage;
double ninjitsu = info.m_Attacker.Skills.Ninjitsu.Value;
double stalkingBonus = Tracking.GetStalkingBonus(info.m_Attacker, info.m_Target);
if (Core.ML)
{
double scalar = (info.m_Attacker.Skills.Hiding.Value +
info.m_Attacker.Skills.Stealth.Value) / 220;
if (scalar > 1)
scalar = 1;
// New formula doesn't apply DamageBonus anymore, caps must be, directly, 60/30.
if (info.m_Steps >= 5)
damage = (int)Math.Floor(Math.Min(60, ninjitsu / 3 * (0.3 + 0.7 * scalar) + stalkingBonus));
else
damage = (int)Math.Floor(Math.Min(30, ninjitsu / 9 * (0.3 + 0.7 * scalar) + stalkingBonus));
if (info.m_isRanged)
damage /= 2;
}
else
{
int divisor = info.m_Steps >= 5 ? 30 : 80;
double baseDamage = ninjitsu / divisor * 10;
int maxDamage = info.m_Steps >= 5 ? 62 : 22;
damage = Math.Clamp((int)(baseDamage + stalkingBonus), 0, maxDamage) + info.m_DamageBonus;
}
if (Core.ML)
info.m_Target.Damage(damage, info.m_Attacker); // Damage is direct.
else
AOS.Damage(info.m_Target, info.m_Attacker, damage, true, 100, 0, 0, 0, 0, 0, 0, false, false,
true); // Damage is physical.
info.m_Timer?.Stop();
m_Table.Remove(info.m_Target);
}
private class DeathStrikeInfo
{
public readonly Mobile m_Attacker;
public readonly int m_DamageBonus;
public readonly bool m_isRanged;
public int m_Steps;
public readonly Mobile m_Target;
public Timer m_Timer;
public DeathStrikeInfo(Mobile target, Mobile attacker, int damageBonus, bool isRanged)
{
m_Target = target;
m_Attacker = attacker;
m_DamageBonus = damageBonus;
m_isRanged = isRanged;
}
}
}
}

View file

@ -2,62 +2,63 @@ using Server.Items;
namespace Server.Spells.Ninjitsu
{
public class FocusAttack : NinjaMove
{
public override int BaseMana => Core.ML ? 10 : 20;
public override double RequiredSkill => Core.ML ? 30.0 : 60;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063095); // You prepare to focus all of your abilities into your next strike.
public override bool Validate(Mobile from)
public class FocusAttack : NinjaMove
{
if (from.FindItemOnLayer(Layer.TwoHanded) is BaseShield)
{
from.SendLocalizedMessage(1063096); // You cannot use this ability while holding a shield.
return false;
}
public override int BaseMana => Core.ML ? 10 : 20;
public override double RequiredSkill => Core.ML ? 30.0 : 60;
Item handOne = from.FindItemOnLayer(Layer.OneHanded) as BaseWeapon;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063095); // You prepare to focus all of your abilities into your next strike.
if (handOne != null && !(handOne is BaseRanged))
return base.Validate(from);
public override bool Validate(Mobile from)
{
if (from.FindItemOnLayer(Layer.TwoHanded) is BaseShield)
{
from.SendLocalizedMessage(1063096); // You cannot use this ability while holding a shield.
return false;
}
Item handTwo = from.FindItemOnLayer(Layer.TwoHanded) as BaseWeapon;
Item handOne = from.FindItemOnLayer(Layer.OneHanded) as BaseWeapon;
if (handTwo != null && !(handTwo is BaseRanged))
return base.Validate(from);
if (handOne != null && !(handOne is BaseRanged))
return base.Validate(from);
from.SendLocalizedMessage(1063097); // You must be wielding a melee weapon without a shield to use this ability.
return false;
Item handTwo = from.FindItemOnLayer(Layer.TwoHanded) as BaseWeapon;
if (handTwo != null && !(handTwo is BaseRanged))
return base.Validate(from);
from.SendLocalizedMessage(1063097); // You must be wielding a melee weapon without a shield to use this ability.
return false;
}
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
var ninjitsu = attacker.Skills.Ninjitsu.Value;
return 1.0 + ninjitsu * ninjitsu / 43636;
}
public override double GetPropertyBonus(Mobile attacker)
{
var ninjitsu = attacker.Skills.Ninjitsu.Value;
var bonus = ninjitsu * ninjitsu / 43636;
return 1.0 + (bonus * 3 + 0.01);
}
public override bool OnBeforeDamage(Mobile attacker, Mobile defender) =>
Validate(attacker) && CheckMana(attacker, true);
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063098); // You focus all of your abilities and strike with deadly force!
attacker.PlaySound(0x510);
CheckGain(attacker);
}
}
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
double ninjitsu = attacker.Skills.Ninjitsu.Value;
return 1.0 + ninjitsu * ninjitsu / 43636;
}
public override double GetPropertyBonus(Mobile attacker)
{
double ninjitsu = attacker.Skills.Ninjitsu.Value;
double bonus = ninjitsu * ninjitsu / 43636;
return 1.0 + (bonus * 3 + 0.01);
}
public override bool OnBeforeDamage(Mobile attacker, Mobile defender) => Validate(attacker) && CheckMana(attacker, true);
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063098); // You focus all of your abilities and strike with deadly force!
attacker.PlaySound(0x510);
CheckGain(attacker);
}
}
}

View file

@ -4,126 +4,127 @@ using Server.Items;
namespace Server.Spells.Ninjitsu
{
public class KiAttack : NinjaMove
{
private static readonly Dictionary<Mobile, KiAttackInfo> m_Table = new Dictionary<Mobile, KiAttackInfo>();
public override int BaseMana => 25;
public override double RequiredSkill => 80.0;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063099); // Your Ki Attack must be complete within 2 seconds for the damage bonus!
public override void OnUse(Mobile from)
public class KiAttack : NinjaMove
{
if (!Validate(from))
return;
private static readonly Dictionary<Mobile, KiAttackInfo> m_Table = new Dictionary<Mobile, KiAttackInfo>();
KiAttackInfo info = new KiAttackInfo(from);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(2.0), EndKiAttack, info);
public override int BaseMana => 25;
public override double RequiredSkill => 80.0;
m_Table[from] = info;
public override TextDefinition AbilityMessage =>
new TextDefinition(1063099); // Your Ki Attack must be complete within 2 seconds for the damage bonus!
public override void OnUse(Mobile from)
{
if (!Validate(from))
return;
var info = new KiAttackInfo(from);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(2.0), EndKiAttack, info);
m_Table[from] = info;
}
public override bool Validate(Mobile from)
{
if (from.Hidden && from.AllowedStealthSteps > 0)
{
from.SendLocalizedMessage(1063127); // You cannot use this ability while in stealth mode.
return false;
}
if (Core.ML && from.Weapon is BaseRanged)
{
from.SendLocalizedMessage(1075858); // You can only use this with melee attacks.
return false;
}
return base.Validate(from);
}
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
if (attacker.Hidden)
return 1.0;
/*
* Pub40 changed pvp damage max to 55%
*/
return 1.0 + GetBonus(attacker) / (Core.ML && attacker.Player && defender.Player ? 40 : 10);
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
if (GetBonus(attacker) == 0.0)
{
attacker.SendLocalizedMessage(1063101); // You were too close to your target to cause any additional damage.
}
else
{
attacker.FixedParticles(0x37BE, 1, 5, 0x26BD, 0x0, 0x1, EffectLayer.Waist);
attacker.PlaySound(0x510);
attacker.SendLocalizedMessage(
1063100
); // Your quick flight to your target causes extra damage as you strike!
defender.FixedParticles(0x37BE, 1, 5, 0x26BD, 0, 0x1, EffectLayer.Waist);
CheckGain(attacker);
}
ClearCurrentMove(attacker);
}
public override void OnClearMove(Mobile from)
{
if (!m_Table.TryGetValue(from, out var info))
return;
info.m_Timer.Stop();
m_Table.Remove(info.m_Mobile);
}
public static double GetBonus(Mobile from)
{
if (!m_Table.TryGetValue(from, out var info))
return 0;
var xDelta = info.m_Location.X - from.X;
var yDelta = info.m_Location.Y - from.Y;
var bonus = Math.Sqrt(xDelta * xDelta + yDelta * yDelta);
if (bonus > 20.0)
bonus = 20.0;
return bonus;
}
private static void EndKiAttack(KiAttackInfo info)
{
info.m_Timer?.Stop();
ClearCurrentMove(info.m_Mobile);
info.m_Mobile.SendLocalizedMessage(1063102); // You failed to complete your Ki Attack in time.
m_Table.Remove(info.m_Mobile);
}
private class KiAttackInfo
{
public readonly Mobile m_Mobile;
public Point3D m_Location;
public Timer m_Timer;
public KiAttackInfo(Mobile m)
{
m_Mobile = m;
m_Location = m.Location;
}
}
}
public override bool Validate(Mobile from)
{
if (from.Hidden && from.AllowedStealthSteps > 0)
{
from.SendLocalizedMessage(1063127); // You cannot use this ability while in stealth mode.
return false;
}
if (Core.ML && from.Weapon is BaseRanged)
{
from.SendLocalizedMessage(1075858); // You can only use this with melee attacks.
return false;
}
return base.Validate(from);
}
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
if (attacker.Hidden)
return 1.0;
/*
* Pub40 changed pvp damage max to 55%
*/
return 1.0 + GetBonus(attacker) / (Core.ML && attacker.Player && defender.Player ? 40 : 10);
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
if (GetBonus(attacker) == 0.0)
{
attacker.SendLocalizedMessage(1063101); // You were too close to your target to cause any additional damage.
}
else
{
attacker.FixedParticles(0x37BE, 1, 5, 0x26BD, 0x0, 0x1, EffectLayer.Waist);
attacker.PlaySound(0x510);
attacker.SendLocalizedMessage(
1063100); // Your quick flight to your target causes extra damage as you strike!
defender.FixedParticles(0x37BE, 1, 5, 0x26BD, 0, 0x1, EffectLayer.Waist);
CheckGain(attacker);
}
ClearCurrentMove(attacker);
}
public override void OnClearMove(Mobile from)
{
if (!m_Table.TryGetValue(from, out KiAttackInfo info))
return;
info.m_Timer.Stop();
m_Table.Remove(info.m_Mobile);
}
public static double GetBonus(Mobile from)
{
if (!m_Table.TryGetValue(from, out KiAttackInfo info))
return 0;
int xDelta = info.m_Location.X - from.X;
int yDelta = info.m_Location.Y - from.Y;
double bonus = Math.Sqrt(xDelta * xDelta + yDelta * yDelta);
if (bonus > 20.0)
bonus = 20.0;
return bonus;
}
private static void EndKiAttack(KiAttackInfo info)
{
info.m_Timer?.Stop();
ClearCurrentMove(info.m_Mobile);
info.m_Mobile.SendLocalizedMessage(1063102); // You failed to complete your Ki Attack in time.
m_Table.Remove(info.m_Mobile);
}
private class KiAttackInfo
{
public Point3D m_Location;
public readonly Mobile m_Mobile;
public Timer m_Timer;
public KiAttackInfo(Mobile m)
{
m_Mobile = m;
m_Location = m.Location;
}
}
}
}

View file

@ -8,248 +8,257 @@ using Server.Spells.Ninjitsu;
namespace Server.Spells.Ninjitsu
{
public class MirrorImage : NinjaSpell
{
private static readonly Dictionary<Mobile, int> m_CloneCount = new Dictionary<Mobile, int>();
private static readonly SpellInfo m_Info = new SpellInfo(
"Mirror Image", null,
-1,
9002);
public MirrorImage(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
public class MirrorImage : NinjaSpell
{
private static readonly Dictionary<Mobile, int> m_CloneCount = new Dictionary<Mobile, int>();
private static readonly SpellInfo m_Info = new SpellInfo(
"Mirror Image",
null,
-1,
9002
);
public MirrorImage(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => Core.ML ? 20.0 : 40.0;
public override int RequiredMana => 10;
public override bool BlockedByAnimalForm => false;
public static bool HasClone(Mobile m) => m_CloneCount.ContainsKey(m);
public static void AddClone(Mobile m)
{
if (m == null)
return;
m_CloneCount[m] = 1 + (m_CloneCount.TryGetValue(m, out var count) ? count : 0);
}
public static void RemoveClone(Mobile m)
{
if (m == null || !m_CloneCount.TryGetValue(m, out var count))
return;
if (count <= 1)
m_CloneCount.Remove(m);
else
m_CloneCount[m]--;
}
public override bool CheckCast()
{
if (Caster.Mounted)
{
Caster.SendLocalizedMessage(1063132); // You cannot use this ability while mounted.
return false;
}
if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(
1063133
); // You cannot summon a mirror image because you have too many followers.
return false;
}
if (TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable) => false;
public override void OnBeginCast()
{
base.OnBeginCast();
Caster.SendLocalizedMessage(1063134); // You begin to summon a mirror image of yourself.
}
public override void OnCast()
{
if (Caster.Mounted)
{
Caster.SendLocalizedMessage(1063132); // You cannot use this ability while mounted.
}
else if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(
1063133
); // You cannot summon a mirror image because you have too many followers.
}
else if (TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
}
else if (CheckSequence())
{
Caster.FixedParticles(0x376A, 1, 14, 0x13B5, EffectLayer.Waist);
Caster.PlaySound(0x511);
new Clone(Caster).MoveToWorld(Caster.Location, Caster.Map);
}
FinishSequence();
}
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.5);
public override double RequiredSkill => Core.ML ? 20.0 : 40.0;
public override int RequiredMana => 10;
public override bool BlockedByAnimalForm => false;
public static bool HasClone(Mobile m) => m_CloneCount.ContainsKey(m);
public static void AddClone(Mobile m)
{
if (m == null)
return;
m_CloneCount[m] = 1 + (m_CloneCount.TryGetValue(m, out int count) ? count : 0);
}
public static void RemoveClone(Mobile m)
{
if (m == null || !m_CloneCount.TryGetValue(m, out int count))
return;
if (count <= 1)
m_CloneCount.Remove(m);
else
m_CloneCount[m]--;
}
public override bool CheckCast()
{
if (Caster.Mounted)
{
Caster.SendLocalizedMessage(1063132); // You cannot use this ability while mounted.
return false;
}
if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(
1063133); // You cannot summon a mirror image because you have too many followers.
return false;
}
if (TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable) => false;
public override void OnBeginCast()
{
base.OnBeginCast();
Caster.SendLocalizedMessage(1063134); // You begin to summon a mirror image of yourself.
}
public override void OnCast()
{
if (Caster.Mounted)
{
Caster.SendLocalizedMessage(1063132); // You cannot use this ability while mounted.
}
else if (Caster.Followers + 1 > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(
1063133); // You cannot summon a mirror image because you have too many followers.
}
else if (TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
}
else if (CheckSequence())
{
Caster.FixedParticles(0x376A, 1, 14, 0x13B5, EffectLayer.Waist);
Caster.PlaySound(0x511);
new Clone(Caster).MoveToWorld(Caster.Location, Caster.Map);
}
FinishSequence();
}
}
}
namespace Server.Mobiles
{
public class Clone : BaseCreature
{
private Mobile m_Caster;
public Clone(Mobile caster) : base(AIType.AI_Melee, FightMode.None, 10, 1, 0.2, 0.4)
public class Clone : BaseCreature
{
m_Caster = caster;
private Mobile m_Caster;
Body = caster.Body;
public Clone(Mobile caster) : base(AIType.AI_Melee, FightMode.None, 10, 1, 0.2, 0.4)
{
m_Caster = caster;
Hue = caster.Hue;
Female = caster.Female;
Body = caster.Body;
Name = caster.Name;
NameHue = caster.NameHue;
Hue = caster.Hue;
Female = caster.Female;
Title = caster.Title;
Kills = caster.Kills;
Name = caster.Name;
NameHue = caster.NameHue;
HairItemID = caster.HairItemID;
HairHue = caster.HairHue;
Title = caster.Title;
Kills = caster.Kills;
FacialHairItemID = caster.FacialHairItemID;
FacialHairHue = caster.FacialHairHue;
HairItemID = caster.HairItemID;
HairHue = caster.HairHue;
for (int i = 0; i < caster.Skills.Length; ++i)
{
Skills[i].Base = caster.Skills[i].Base;
Skills[i].Cap = caster.Skills[i].Cap;
}
FacialHairItemID = caster.FacialHairItemID;
FacialHairHue = caster.FacialHairHue;
for (int i = 0; i < caster.Items.Count; i++) AddItem(CloneItem(caster.Items[i]));
for (var i = 0; i < caster.Skills.Length; ++i)
{
Skills[i].Base = caster.Skills[i].Base;
Skills[i].Cap = caster.Skills[i].Cap;
}
Warmode = true;
for (var i = 0; i < caster.Items.Count; i++) AddItem(CloneItem(caster.Items[i]));
Summoned = true;
SummonMaster = caster;
Warmode = true;
ControlOrder = OrderType.Follow;
ControlTarget = caster;
Summoned = true;
SummonMaster = caster;
TimeSpan duration = TimeSpan.FromSeconds(30 + caster.Skills.Ninjitsu.Fixed / 40);
ControlOrder = OrderType.Follow;
ControlTarget = caster;
new UnsummonTimer(caster, this, duration).Start();
SummonEnd = DateTime.UtcNow + duration;
var duration = TimeSpan.FromSeconds(30 + caster.Skills.Ninjitsu.Fixed / 40);
MirrorImage.AddClone(m_Caster);
new UnsummonTimer(caster, this, duration).Start();
SummonEnd = DateTime.UtcNow + duration;
MirrorImage.AddClone(m_Caster);
}
public Clone(Serial serial) : base(serial)
{
}
protected override BaseAI ForcedAI => new CloneAI(this);
public override bool DeleteCorpseOnDeath => true;
public override bool IsDispellable => false;
public override bool Commandable => false;
public override bool IsHumanInTown() => false;
private Item CloneItem(Item item)
{
var newItem = new Item(item.ItemID);
newItem.Hue = item.Hue;
newItem.Layer = item.Layer;
return newItem;
}
public override void OnDamage(int amount, Mobile from, bool willKill)
{
Delete();
}
public override void OnDelete()
{
Effects.SendLocationParticles(
EffectItem.Create(Location, Map, EffectItem.DefaultDuration),
0x3728,
10,
15,
5042
);
base.OnDelete();
}
public override void OnAfterDelete()
{
MirrorImage.RemoveClone(m_Caster);
base.OnAfterDelete();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
writer.Write(m_Caster);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadEncodedInt();
m_Caster = reader.ReadMobile();
MirrorImage.AddClone(m_Caster);
}
}
public Clone(Serial serial) : base(serial)
{
}
protected override BaseAI ForcedAI => new CloneAI(this);
public override bool DeleteCorpseOnDeath => true;
public override bool IsDispellable => false;
public override bool Commandable => false;
public override bool IsHumanInTown() => false;
private Item CloneItem(Item item)
{
Item newItem = new Item(item.ItemID);
newItem.Hue = item.Hue;
newItem.Layer = item.Layer;
return newItem;
}
public override void OnDamage(int amount, Mobile from, bool willKill)
{
Delete();
}
public override void OnDelete()
{
Effects.SendLocationParticles(EffectItem.Create(Location, Map, EffectItem.DefaultDuration), 0x3728, 10, 15,
5042);
base.OnDelete();
}
public override void OnAfterDelete()
{
MirrorImage.RemoveClone(m_Caster);
base.OnAfterDelete();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
writer.Write(m_Caster);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
m_Caster = reader.ReadMobile();
MirrorImage.AddClone(m_Caster);
}
}
}
namespace Server.Mobiles
{
public class CloneAI : BaseAI
{
public CloneAI(Clone m) : base(m) => m.CurrentSpeed = m.ActiveSpeed;
public override bool CanDetectHidden => false;
public override bool Think()
public class CloneAI : BaseAI
{
// Clones only follow their owners
Mobile master = m_Mobile.SummonMaster;
public CloneAI(Clone m) : base(m) => m.CurrentSpeed = m.ActiveSpeed;
if (master?.Map == m_Mobile.Map && master?.InRange(m_Mobile, m_Mobile.RangePerception) == true)
{
int iCurrDist = (int)m_Mobile.GetDistanceToSqrt(master);
bool bRun = iCurrDist > 5;
public override bool CanDetectHidden => false;
WalkMobileRange(master, 2, bRun, 0, 1);
}
else
{
WalkRandom(2, 2, 1);
}
public override bool Think()
{
// Clones only follow their owners
var master = m_Mobile.SummonMaster;
return true;
if (master?.Map == m_Mobile.Map && master?.InRange(m_Mobile, m_Mobile.RangePerception) == true)
{
var iCurrDist = (int)m_Mobile.GetDistanceToSqrt(master);
var bRun = iCurrDist > 5;
WalkMobileRange(master, 2, bRun, 0, 1);
}
else
{
WalkRandom(2, 2, 1);
}
return true;
}
}
}
}

View file

@ -1,12 +1,12 @@
namespace Server.Spells
{
public class NinjaMove : SpecialMove
{
public override SkillName MoveSkill => SkillName.Ninjitsu;
public override void CheckGain(Mobile m)
public class NinjaMove : SpecialMove
{
m.CheckSkill(MoveSkill, RequiredSkill - 12.5, RequiredSkill + 37.5); // Per five on friday 02/16/07
public override SkillName MoveSkill => SkillName.Ninjitsu;
public override void CheckGain(Mobile m)
{
m.CheckSkill(MoveSkill, RequiredSkill - 12.5, RequiredSkill + 37.5); // Per five on friday 02/16/07
}
}
}
}
}

View file

@ -2,93 +2,102 @@ using Server.Mobiles;
namespace Server.Spells.Ninjitsu
{
public abstract class NinjaSpell : Spell
{
public NinjaSpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info)
public abstract class NinjaSpell : Spell
{
public NinjaSpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info)
{
}
public abstract double RequiredSkill { get; }
public abstract int RequiredMana { get; }
public override SkillName CastSkill => SkillName.Ninjitsu;
public override SkillName DamageSkill => SkillName.Ninjitsu;
public override bool RevealOnCast => false;
public override bool ClearHandsOnCast => false;
public override bool ShowHandMovement => false;
public override bool BlocksMovement => false;
// public override int CastDelayBase => 1;
public override int CastRecoveryBase => 7;
public static bool CheckExpansion(Mobile from) =>
(from as PlayerMobile)?.NetState?.SupportsExpansion(Expansion.SE) == true;
public override bool CheckCast()
{
var mana = ScaleMana(RequiredMana);
if (!base.CheckCast())
return false;
if (!CheckExpansion(Caster))
{
Caster.SendLocalizedMessage(1063456); // You must upgrade to Samurai Empire in order to use that ability.
return false;
}
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
var args = $"{RequiredSkill:F1}\t{CastSkill.ToString()}\t ";
Caster.SendLocalizedMessage(
1063013,
args
); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(
1060174,
mana.ToString()
); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
return true;
}
public override bool CheckFizzle()
{
var mana = ScaleMana(RequiredMana);
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
Caster.SendLocalizedMessage(
1063352,
RequiredSkill.ToString("F1")
); // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(
1060174,
mana.ToString()
); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
if (!base.CheckFizzle())
return false;
Caster.Mana -= mana;
return true;
}
public override void GetCastSkills(out double min, out double max)
{
min = RequiredSkill - 12.5; // Per 5 on friday 2/16/07
max = RequiredSkill + 37.5;
}
public override int GetMana() => 0;
}
public abstract double RequiredSkill { get; }
public abstract int RequiredMana { get; }
public override SkillName CastSkill => SkillName.Ninjitsu;
public override SkillName DamageSkill => SkillName.Ninjitsu;
public override bool RevealOnCast => false;
public override bool ClearHandsOnCast => false;
public override bool ShowHandMovement => false;
public override bool BlocksMovement => false;
// public override int CastDelayBase => 1;
public override int CastRecoveryBase => 7;
public static bool CheckExpansion(Mobile from) => (from as PlayerMobile)?.NetState?.SupportsExpansion(Expansion.SE) == true;
public override bool CheckCast()
{
int mana = ScaleMana(RequiredMana);
if (!base.CheckCast())
return false;
if (!CheckExpansion(Caster))
{
Caster.SendLocalizedMessage(1063456); // You must upgrade to Samurai Empire in order to use that ability.
return false;
}
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
string args = $"{RequiredSkill:F1}\t{CastSkill.ToString()}\t ";
Caster.SendLocalizedMessage(1063013,
args); // You need at least ~1_SKILL_REQUIREMENT~ ~2_SKILL_NAME~ skill to use that ability.
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
return true;
}
public override bool CheckFizzle()
{
int mana = ScaleMana(RequiredMana);
if (Caster.Skills[CastSkill].Value < RequiredSkill)
{
Caster.SendLocalizedMessage(1063352,
RequiredSkill.ToString("F1")); // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
return false;
}
if (Caster.Mana < mana)
{
Caster.SendLocalizedMessage(1060174,
mana.ToString()); // You must have at least ~1_MANA_REQUIREMENT~ Mana to use this ability.
return false;
}
if (!base.CheckFizzle())
return false;
Caster.Mana -= mana;
return true;
}
public override void GetCastSkills(out double min, out double max)
{
min = RequiredSkill - 12.5; // Per 5 on friday 2/16/07
max = RequiredSkill + 37.5;
}
public override int GetMana() => 0;
}
}

View file

@ -9,102 +9,109 @@ using Server.Targeting;
namespace Server.Spells.Ninjitsu
{
public class Shadowjump : NinjaSpell, ISpellTargetingPoint3D
{
private static readonly SpellInfo m_Info = new SpellInfo(
"Shadowjump", null,
-1,
9002);
public Shadowjump(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
public class Shadowjump : NinjaSpell, ISpellTargetingPoint3D
{
private static readonly SpellInfo m_Info = new SpellInfo(
"Shadowjump",
null,
-1,
9002
);
public Shadowjump(Mobile caster, Item scroll) : base(caster, scroll, m_Info)
{
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 50.0;
public override int RequiredMana => 15;
public override bool BlockedByAnimalForm => false;
public void Target(IPoint3D p)
{
var orig = p;
var map = Caster.Map;
SpellHelper.GetSurfaceTop(ref p);
var from = Caster.Location;
var to = new Point3D(p);
var pm = Caster as PlayerMobile; // IsStealthing should be moved to Server.Mobiles
if (!pm.IsStealthing)
{
Caster.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
}
else if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (WeightOverloading.IsOverloaded(Caster))
{
Caster.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
}
else if (!SpellHelper.CheckTravel(Caster, TravelCheckType.TeleportFrom) ||
!SpellHelper.CheckTravel(Caster, map, to, TravelCheckType.TeleportTo))
{
}
else if (map?.CanSpawnMobile(p.X, p.Y, p.Z) != true)
{
Caster.SendLocalizedMessage(502831); // Cannot teleport to that spot.
}
else if (SpellHelper.CheckMulti(to, map, true, 5))
{
Caster.SendLocalizedMessage(502831); // Cannot teleport to that spot.
}
else if (Region.Find(to, map).IsPartOf<HouseRegion>())
{
Caster.SendLocalizedMessage(502829); // Cannot teleport to that spot.
}
else if (CheckSequence())
{
SpellHelper.Turn(Caster, orig);
var m = Caster;
m.Location = to;
m.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(from, m.Map, EffectItem.DefaultDuration),
0x3728,
10,
10,
2023
);
m.PlaySound(0x512);
Stealth.OnUse(m); // stealth check after the a jump
}
FinishSequence();
}
public override bool CheckCast()
{
// IsStealthing should be moved to Server.Mobiles
if ((Caster as PlayerMobile)?.IsStealthing != true)
{
Caster.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable) => false;
public override void OnCast()
{
Caster.SendLocalizedMessage(1063088); // You prepare to perform a Shadowjump.
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, 11);
}
}
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(1.0);
public override double RequiredSkill => 50.0;
public override int RequiredMana => 15;
public override bool BlockedByAnimalForm => false;
public override bool CheckCast()
{
// IsStealthing should be moved to Server.Mobiles
if ((Caster as PlayerMobile)?.IsStealthing != true)
{
Caster.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable) => false;
public override void OnCast()
{
Caster.SendLocalizedMessage(1063088); // You prepare to perform a Shadowjump.
Caster.Target = new SpellTargetPoint3D(this, TargetFlags.None, 11);
}
public void Target(IPoint3D p)
{
IPoint3D orig = p;
Map map = Caster.Map;
SpellHelper.GetSurfaceTop(ref p);
Point3D from = Caster.Location;
Point3D to = new Point3D(p);
PlayerMobile pm = Caster as PlayerMobile; // IsStealthing should be moved to Server.Mobiles
if (!pm.IsStealthing)
{
Caster.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
}
else if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (WeightOverloading.IsOverloaded(Caster))
{
Caster.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
}
else if (!SpellHelper.CheckTravel(Caster, TravelCheckType.TeleportFrom) ||
!SpellHelper.CheckTravel(Caster, map, to, TravelCheckType.TeleportTo))
{
}
else if (map?.CanSpawnMobile(p.X, p.Y, p.Z) != true)
{
Caster.SendLocalizedMessage(502831); // Cannot teleport to that spot.
}
else if (SpellHelper.CheckMulti(to, map, true, 5))
{
Caster.SendLocalizedMessage(502831); // Cannot teleport to that spot.
}
else if (Region.Find(to, map).IsPartOf<HouseRegion>())
{
Caster.SendLocalizedMessage(502829); // Cannot teleport to that spot.
}
else if (CheckSequence())
{
SpellHelper.Turn(Caster, orig);
Mobile m = Caster;
m.Location = to;
m.ProcessDelta();
Effects.SendLocationParticles(EffectItem.Create(from, m.Map, EffectItem.DefaultDuration), 0x3728, 10, 10,
2023);
m.PlaySound(0x512);
Stealth.OnUse(m); // stealth check after the a jump
}
FinishSequence();
}
}
}

View file

@ -4,110 +4,111 @@ using Server.SkillHandlers;
namespace Server.Spells.Ninjitsu
{
public class SurpriseAttack : NinjaMove
{
private static readonly Dictionary<Mobile, SurpriseAttackInfo> m_Table = new Dictionary<Mobile, SurpriseAttackInfo>();
public override int BaseMana => 20;
public override double RequiredSkill => Core.ML ? 60.0 : 30.0;
public override TextDefinition AbilityMessage => new TextDefinition(1063128); // You prepare to surprise your prey.
public override bool ValidatesDuringHit => false;
public override bool Validate(Mobile from)
public class SurpriseAttack : NinjaMove
{
if (!from.Hidden || from.AllowedStealthSteps <= 0)
{
from.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
private static readonly Dictionary<Mobile, SurpriseAttackInfo>
m_Table = new Dictionary<Mobile, SurpriseAttackInfo>();
return base.Validate(from);
public override int BaseMana => 20;
public override double RequiredSkill => Core.ML ? 60.0 : 30.0;
public override TextDefinition AbilityMessage => new TextDefinition(1063128); // You prepare to surprise your prey.
public override bool ValidatesDuringHit => false;
public override bool Validate(Mobile from)
{
if (!from.Hidden || from.AllowedStealthSteps <= 0)
{
from.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;
}
return base.Validate(from);
}
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
var valid = Validate(attacker) && CheckMana(attacker, true);
if (valid)
{
attacker.BeginAction<Stealth>();
Timer.DelayCall(TimeSpan.FromSeconds(5.0), attacker.EndAction<Stealth>);
}
return valid;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
// Validates before swing
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063129); // You catch your opponent off guard with your Surprise Attack!
defender.SendLocalizedMessage(1063130); // Your defenses are lowered as your opponent surprises you!
defender.FixedParticles(0x37B9, 1, 5, 0x26DA, 0, 3, EffectLayer.Head);
attacker.RevealingAction();
if (m_Table.TryGetValue(defender, out var info))
{
info.m_Timer?.Stop();
m_Table.Remove(defender);
}
var ninjitsu = attacker.Skills.Ninjitsu.Fixed;
var malus = ninjitsu / 60 + (int)Tracking.GetStalkingBonus(attacker, defender);
info = new SurpriseAttackInfo(defender, malus);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(8.0), EndSurprise, info);
m_Table[defender] = info;
CheckGain(attacker);
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063161); // You failed to properly use the element of surprise.
attacker.RevealingAction();
}
public static bool GetMalus(Mobile target, ref int malus)
{
if (!m_Table.TryGetValue(target, out var info))
return false;
malus = info.m_Malus;
return true;
}
private static void EndSurprise(SurpriseAttackInfo info)
{
info.m_Timer?.Stop();
info.m_Target.SendLocalizedMessage(1063131); // Your defenses have returned to normal.
m_Table.Remove(info.m_Target);
}
private class SurpriseAttackInfo
{
public readonly int m_Malus;
public readonly Mobile m_Target;
public Timer m_Timer;
public SurpriseAttackInfo(Mobile target, int effect)
{
m_Target = target;
m_Malus = effect;
}
}
}
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
bool valid = Validate(attacker) && CheckMana(attacker, true);
if (valid)
{
attacker.BeginAction<Stealth>();
Timer.DelayCall(TimeSpan.FromSeconds(5.0), attacker.EndAction<Stealth>);
}
return valid;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
// Validates before swing
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063129); // You catch your opponent off guard with your Surprise Attack!
defender.SendLocalizedMessage(1063130); // Your defenses are lowered as your opponent surprises you!
defender.FixedParticles(0x37B9, 1, 5, 0x26DA, 0, 3, EffectLayer.Head);
attacker.RevealingAction();
if (m_Table.TryGetValue(defender, out SurpriseAttackInfo info))
{
info.m_Timer?.Stop();
m_Table.Remove(defender);
}
int ninjitsu = attacker.Skills.Ninjitsu.Fixed;
int malus = ninjitsu / 60 + (int)Tracking.GetStalkingBonus(attacker, defender);
info = new SurpriseAttackInfo(defender, malus);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(8.0), EndSurprise, info);
m_Table[defender] = info;
CheckGain(attacker);
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
ClearCurrentMove(attacker);
attacker.SendLocalizedMessage(1063161); // You failed to properly use the element of surprise.
attacker.RevealingAction();
}
public static bool GetMalus(Mobile target, ref int malus)
{
if (!m_Table.TryGetValue(target, out SurpriseAttackInfo info))
return false;
malus = info.m_Malus;
return true;
}
private static void EndSurprise(SurpriseAttackInfo info)
{
info.m_Timer?.Stop();
info.m_Target.SendLocalizedMessage(1063131); // Your defenses have returned to normal.
m_Table.Remove(info.m_Target);
}
private class SurpriseAttackInfo
{
public readonly int m_Malus;
public readonly Mobile m_Target;
public Timer m_Timer;
public SurpriseAttackInfo(Mobile target, int effect)
{
m_Target = target;
m_Malus = effect;
}
}
}
}