fix: Adds missing ML Abilities (#799)

This commit is contained in:
IAmDanielDinner 2021-10-04 08:04:06 +02:00 committed by GitHub
parent 4893094bb4
commit 5725fba373
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 931 additions and 243 deletions

View file

@ -10,18 +10,7 @@ namespace Server.Items
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)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{

View file

@ -0,0 +1,106 @@
using Server.Mobiles;
using System;
using System.Collections.Generic;
namespace Server.Items
{
public class Bladeweave : WeaponAbility
{
private class BladeWeaveRedirect
{
public readonly WeaponAbility NewAbility;
public readonly int ClilocEntry;
public BladeWeaveRedirect(WeaponAbility ability, int cliloc)
{
NewAbility = ability;
ClilocEntry = cliloc;
}
}
private static readonly Dictionary<Mobile, BladeWeaveRedirect> _newAttack = new();
public static bool BladeWeaving(Mobile attacker, out WeaponAbility a)
{
if (_newAttack.TryGetValue(attacker, out var bwr))
{
a = bwr.NewAbility;
return true;
}
a = null;
return false;
}
public override int BaseMana => 30;
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
if (!Validate(attacker) || !CheckMana(attacker, false))
{
return false;
}
// Bladeweave is only from 90 fighting and tactics skill, so you need only to check bushido/ninjitsu value over 50 to perform block and feint.
var requiredBushido = Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value);
var ran = Utility.Random(requiredBushido >= 50 ? 9 : 7);
var newAttack = _newAttack[attacker] = GetBladeWeaveRedirect(ran);
return newAttack.NewAbility.OnBeforeSwing(attacker, defender);
}
private static BladeWeaveRedirect GetBladeWeaveRedirect(int number) =>
number switch
{
0 => new BladeWeaveRedirect(ArmorIgnore, 1028838),
1 => new BladeWeaveRedirect(BleedAttack, 1028839),
2 => new BladeWeaveRedirect(ConcussionBlow, 1028840),
3 => new BladeWeaveRedirect(CrushingBlow, 1028841),
4 => new BladeWeaveRedirect(DoubleStrike, 1028844),
5 => new BladeWeaveRedirect(MortalStrike, 1028846),
6 => new BladeWeaveRedirect(ParalyzingBlow, 1028848),
7 => new BladeWeaveRedirect(Block, 1028853),
_ => new BladeWeaveRedirect(Feint, 1028857) //8
};
public override bool OnBeforeDamage(Mobile attacker, Mobile defender) =>
_newAttack.TryGetValue(attacker, out var bwr)
? bwr.NewAbility.OnBeforeDamage(attacker, defender)
: base.OnBeforeDamage(attacker, defender);
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (CheckMana(attacker, false))
{
if (_newAttack.TryGetValue(attacker, out var bwr))
{
attacker.SendLocalizedMessage(1072841, $"#{bwr.ClilocEntry}");
bwr.NewAbility.OnHit(attacker, defender, damage);
}
else
{
base.OnHit(attacker, defender, damage);
}
_newAttack.Remove(attacker);
ClearCurrentAbility(attacker);
}
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
if (_newAttack.TryGetValue(attacker, out var bwr))
{
bwr.NewAbility.OnMiss(attacker, defender);
}
else
{
base.OnMiss(attacker, defender);
}
_newAttack.Remove(attacker);
}
}
}

View file

@ -11,18 +11,7 @@ namespace Server.Items
private static readonly Dictionary<Mobile, InternalTimer> _table = new();
public override int BaseMana => 30;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{

View file

@ -8,6 +8,24 @@ namespace Server.Items
public override int BaseMana => 25;
public override double DamageScalar => 1.5;
public virtual double GetRequiredTactics(Mobile from)
{
if (from.Weapon is BaseWeapon weapon)
{
if (weapon.PrimaryAbility == this)
{
return 30.0;
}
if (weapon.SecondaryAbility == this)
{
return 60.0;
}
}
return 200.0;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))

View file

@ -12,18 +12,7 @@ namespace Server.Items
private static readonly Dictionary<Mobile, DefenseMasteryInfo> _table = new();
public override int BaseMana => 30;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
@ -40,8 +29,7 @@ namespace Server.Items
var modifier =
(int)(30.0 *
((Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value) -
50.0) / 70.0));
((Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value) - 50.0) / 70.0));
if (_table.TryGetValue(attacker, out var info))
{

View file

@ -12,33 +12,25 @@ namespace Server.Items
public override int BaseMana => 20;
// No longer active in pub21:
/*public override bool CheckSkills( Mobile from )
public override bool RequiresTactics(Mobile from) => from.Weapon is not BaseWeapon { Skill: SkillName.Wrestling };
// Disarm is special. Doesnt need tactics when wresling and tactics need is lower than fighting skill.
public virtual double GetRequiredTactics(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))
if (from.Weapon is BaseWeapon weapon)
{
return false;
if (weapon.PrimaryAbility == this)
{
return 30.0;
}
if (weapon.SecondaryAbility == this)
{
return 60.0;
}
}
return weapon.Skill != SkillName.Wrestling;
return 200.0;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
@ -52,7 +44,7 @@ namespace Server.Items
var toDisarm = defender.FindItemOnLayer(Layer.OneHanded);
if (toDisarm?.Movable == false)
if (toDisarm?.Movable != false)
{
toDisarm = defender.FindItemOnLayer(Layer.TwoHanded);
}
@ -63,7 +55,7 @@ namespace Server.Items
{
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
}
else if (!Core.ML && toDisarm == null || toDisarm is BaseShield || toDisarm is Spellbook)
else if (!Core.ML && toDisarm == null || toDisarm is BaseShield or Spellbook)
{
attacker.SendLocalizedMessage(1060849); // Your target is already unarmed!
}
@ -77,6 +69,8 @@ namespace Server.Items
pack.DropItem(toDisarm);
BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.NoRearm, 1075637, BlockEquipDuration, defender));
BaseWeapon.BlockEquip(defender, BlockEquipDuration);
}
}

View file

@ -44,8 +44,8 @@ namespace Server.Items
return;
}
if (attacker.Mounted && (!(attacker.Weapon is Lance) || !(defender.Weapon is Lance))
) // TODO: Should there be a message here?
// TODO: Should there be a message here?
if (attacker.Mounted && (attacker.Weapon is not Lance || defender.Weapon is not Lance))
{
return;
}
@ -101,12 +101,9 @@ namespace Server.Items
{
playerMobile.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, true);
}
else if (Core.ML && attacker is BaseCreature bc)
else if (Core.ML && attacker is BaseCreature { ControlMaster: PlayerMobile pm })
{
if (bc.ControlMaster is PlayerMobile pm)
{
pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
}
pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
}
if (!attacker.Mounted)

View file

@ -1,3 +1,5 @@
using System;
namespace Server.Items
{
/// <summary>
@ -7,17 +9,8 @@ namespace Server.Items
{
public override int BaseMana => 30;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresTactics(Mobile from) => false;
public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{

View file

@ -12,17 +12,8 @@ namespace Server.Items
public override int BaseMana => 30;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063352, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Ninjitsu;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
@ -44,8 +35,8 @@ namespace Server.Items
timer = new DualWieldTimer(
attacker,
(int)(20.0 + 3.0 * (attacker.Skills.Ninjitsu.Value - 50.0) / 7.0)
); // 20-50 % increase
(int)(20.0 + 3.0 * (attacker.Skills.Ninjitsu.Value - 50.0) / 7.0) // 20-50 % increase
);
timer.Start();
Registry.Add(attacker, timer);
@ -55,8 +46,7 @@ namespace Server.Items
{
private readonly Mobile m_Owner;
public DualWieldTimer(Mobile owner, int bonusSwingSpeed)
: base(TimeSpan.FromSeconds(6.0))
public DualWieldTimer(Mobile owner, int bonusSwingSpeed) : base(TimeSpan.FromSeconds(6.0))
{
m_Owner = owner;
BonusSwingSpeed = bonusSwingSpeed;

View file

@ -11,18 +11,7 @@ namespace Server.Items
public static Dictionary<Mobile, FeintTimer> Registry { get; } = new();
public override int BaseMana => 30;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
@ -46,10 +35,8 @@ namespace Server.Items
timer = new FeintTimer(
defender,
(int)(20.0 + 3.0 * (Math.Max(
attacker.Skills.Ninjitsu.Value,
attacker.Skills.Bushido.Value
) - 50.0) / 7.0)
(int)(20.0 + 3.0 *
(Math.Max(attacker.Skills.Ninjitsu.Value, attacker.Skills.Bushido.Value) - 50.0) / 7.0)
); // 20-50 % decrease
timer.Start();

View file

@ -0,0 +1,164 @@
using Server.Spells;
using System;
using System.Collections.Generic;
namespace Server.Items
{
public class ForceArrow : WeaponAbility
{
public override int BaseMana => 20;
public override bool RequiresTactics(Mobile from) => false;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
ClearCurrentAbility(attacker);
attacker.SendLocalizedMessage(1074381); // You fire an arrow of pure force.
defender.SendLocalizedMessage(1074382); // You are struck by a force arrow!
if (0.4 > Utility.RandomDouble())
{
defender.Combatant = null;
defender.Warmode = false;
}
ForceArrowInfo info = GetInfo(attacker, defender);
if (info == null)
{
BeginForceArrow(attacker, defender);
}
else if (info.Timer.Running)
{
info.Timer.IncreaseExpiration();
BuffInfo.RemoveBuff(defender, BuffIcon.ForceArrow);
BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.ForceArrow, 1151285, 1151286, info.DefenseChanceMalus.ToString()));
}
if (defender.Spell is Spell spell && spell.IsCasting)
{
spell.Disturb(DisturbType.Hurt, false, true);
}
}
private static readonly Dictionary<Mobile, List<ForceArrowInfo>> _table = new Dictionary<Mobile, List<ForceArrowInfo>>();
public static void BeginForceArrow(Mobile attacker, Mobile defender)
{
ForceArrowInfo info = new ForceArrowInfo(attacker, defender);
info.Timer = new ForceArrowTimer(info);
if (_table.TryGetValue(attacker, out var list))
{
list.Add(info);
}
else
{
_table.Add(attacker, new List<ForceArrowInfo>() { info });
}
BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.ForceArrow, 1151285, 1151286, info.DefenseChanceMalus.ToString()));
}
public static void EndForceArrow(ForceArrowInfo info)
{
if (info == null)
{
return;
}
Mobile attacker = info.Attacker;
if (_table.TryGetValue(attacker, out var list) && list.Remove(info) && list.Count == 0)
{
_table.Remove(attacker);
}
BuffInfo.RemoveBuff(info.Defender, BuffIcon.ForceArrow);
}
public static bool HasForceArrow(Mobile attacker, Mobile defender)
{
if (_table.TryGetValue(attacker, out var list))
{
foreach (ForceArrowInfo info in list)
{
if (info.Defender == defender)
{
return true;
}
}
}
return false;
}
public static ForceArrowInfo GetInfo(Mobile attacker, Mobile defender)
{
if (_table.TryGetValue(attacker,out var list))
{
foreach (ForceArrowInfo info in list)
{
if (info.Defender == defender)
{
return info;
}
}
}
return null;
}
public class ForceArrowInfo
{
public Mobile Attacker { get; }
public Mobile Defender { get; }
public ForceArrowTimer Timer { get; set; }
public int DefenseChanceMalus { get; set; }
public ForceArrowInfo(Mobile attacker, Mobile defender)
{
Attacker = attacker;
Defender = defender;
DefenseChanceMalus = 10;
}
}
public class ForceArrowTimer : Timer
{
private readonly ForceArrowInfo _info;
private DateTime _expires;
public ForceArrowTimer(ForceArrowInfo info)
: base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1))
{
_info = info;
_expires = Core.Now + TimeSpan.FromSeconds(10);
Start();
}
protected override void OnTick()
{
if (_expires < Core.Now)
{
Stop();
EndForceArrow(_info);
}
}
public void IncreaseExpiration()
{
_expires += TimeSpan.FromSeconds(2);
_info.DefenseChanceMalus += 5;
}
}
}
}

View file

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
namespace Server.Items
{
public class ForceOfNature : WeaponAbility
{
public override int BaseMana => 35;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
ClearCurrentAbility(attacker);
attacker.SendLocalizedMessage(1074374); // You attack your enemy with the force of nature!
defender.SendLocalizedMessage(1074375); // You are assaulted with great force!
defender.PlaySound(0x22F);
defender.FixedParticles(0x36CB, 1, 9, 9911, 67, 5, EffectLayer.Head);
defender.FixedParticles(0x374A, 1, 17, 9502, 1108, 4, (EffectLayer)255);
Remove(attacker);
ForceOfNatureTimer t = new ForceOfNatureTimer(attacker, defender);
t.Start();
_table[attacker] = t;
}
private static readonly Dictionary<Mobile, ForceOfNatureTimer> _table = new Dictionary<Mobile, ForceOfNatureTimer>();
public static void Remove(Mobile m)
{
if (_table.Remove(m, out var timer))
{
timer.Stop();
}
}
public static void OnHit(Mobile from, Mobile target)
{
if (_table.TryGetValue(from,out var t))
{
t.Hits++;
t.LastHit = Core.Now;
if (t.Hits % 12 == 0)
{
int duration = target.Skills[SkillName.MagicResist].Value >= 90.0 ? 1 : 2;
target.Paralyze(TimeSpan.FromSeconds(duration));
target.FixedEffect(0x376A, 9, 32);
target.PlaySound(0x204);
t.Hits = 0;
from.SendLocalizedMessage(1004013); // You successfully stun your opponent!
target.SendLocalizedMessage(1004014); // You have been stunned!
}
}
}
public static double GetDamageScalar(Mobile from, Mobile target)
{
if (_table.TryGetValue(from, out var t) && t.Target == target)
{
var bonus = Math.Min(100, Math.Max(50, from.Str - 50));
return (100.0 + bonus) / 100.0;
}
return 1.0;
}
private class ForceOfNatureTimer : Timer
{
public Mobile Target { get; }
public Mobile From { get; }
public int Hits { get; set; }
public DateTime LastHit { get; set; }
public ForceOfNatureTimer(Mobile from, Mobile target)
: base(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
{
Target = target;
From = from;
Hits = 1;
LastHit = Core.Now;
}
protected override void OnTick()
{
if (!From.Alive || !Target.Alive || Target.Map != From.Map || Target.GetDistanceToSqrt(From.Location) > 10 || LastHit + TimeSpan.FromSeconds(20) < Core.Now || Index > 36)
{
Remove(From);
return;
}
if (Index == 1)
{
int damage = Utility.RandomMinMax(15, 35);
AOS.Damage(From, From, damage, false, 0, 0, 0, 0, 0, 0, 100, false, false, false);
}
}
}
}
}

View file

@ -12,18 +12,8 @@ namespace Server.Items
public override int BaseMana => 30;
public static Dictionary<Mobile, FrenziedWirlwindTimer> Registry { get; } = new();
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresTactics(Mobile from) => false;
public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{

View file

@ -30,7 +30,7 @@ namespace Server.Items
ClearCurrentAbility(attacker);
if (!(attacker.Weapon is BaseWeapon weapon))
if (attacker.Weapon is not BaseWeapon weapon)
{
return;
}

View file

@ -0,0 +1,58 @@
using Server.Spells;
using System;
using System.Collections.Generic;
namespace Server.Items
{
public class LightningArrow : WeaponAbility
{
public override int BaseMana => 20;
//TODO - add ConsumeAmmo on weaponabilities
// public override bool ConsumeAmmo => false;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker))
{
return;
}
ClearCurrentAbility(attacker);
Map map = attacker.Map;
if (map == null || attacker.Weapon is not BaseWeapon weapon || !CheckMana(attacker, true))
{
return;
}
List<Mobile> targets = new List<Mobile>();
IPooledEnumerable eable = defender.GetMobilesInRange(5);
foreach (Mobile m in eable)
{
if (m != defender && m != attacker && SpellHelper.ValidIndirectTarget(attacker, m) && m?.Deleted == false &&
m.Map == attacker.Map && m.Alive && attacker.CanSee(m) && attacker.CanBeHarmful(m) &&
attacker.InRange(m, weapon.MaxRange) && attacker.InLOS(m))
{
targets.Add(m);
}
}
eable.Free();
defender.BoltEffect(0);
var mobilesLeft = Math.Min(targets.Count, 2);
while (mobilesLeft-- > 0)
{
var index = Utility.Random(targets.Count);
var m = targets[index];
targets.RemoveAt(index);
m.BoltEffect(0);
AOS.Damage(m, attacker, Utility.RandomMinMax(29, 40), 0, 0, 0, 0, 100);
}
}
}
}

View file

@ -9,17 +9,8 @@ namespace Server.Items
{
public override int BaseMana => 30;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Bushido) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
from.SendLocalizedMessage(1070768, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Bushido;
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{

View file

@ -17,25 +17,7 @@ namespace Server.Items
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;
}*/
// When using Wrestling, tactics isnt needed.
public override bool RequiresTactics(Mobile from) =>
!(from.Weapon is BaseWeapon weapon && weapon.Skill == SkillName.Wrestling);

View file

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
namespace Server.Items
{
public class PsychicAttack : WeaponAbility
{
public static Dictionary<Mobile, PsychicAttackTimer> Registry { get; } = new();
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(1074383); // Your shot sends forth a wave of psychic energy.
defender.SendLocalizedMessage(1074384); // Your mind is attacked by psychic force!
defender.FixedParticles(0x3789, 10, 25, 5032, EffectLayer.Head);
defender.PlaySound(0x1F8);
if (Registry.TryGetValue(defender, out var timer))
{
if (!timer.DoneIncrease)
{
timer.SpellDamageMalus *= 2;
timer.ManaCostMalus *= 2;
}
}
else
{
timer = new PsychicAttackTimer(defender);
timer.Start();
Registry.Add(defender, timer);
}
BuffInfo.RemoveBuff(defender, BuffIcon.PsychicAttack);
string args = $"{timer.SpellDamageMalus}\t{timer.ManaCostMalus}";
BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.PsychicAttack, 1151296, 1151297, args));
}
public static void RemoveEffects(Mobile defender)
{
if (defender == null)
{
return;
}
BuffInfo.RemoveBuff(defender, BuffIcon.PsychicAttack);
Registry.Remove(defender);
defender.SendLocalizedMessage(1150292); // You recover from the effects of the psychic attack.
}
public class PsychicAttackTimer : Timer
{
private readonly Mobile _defender;
private int _spellDamageMalus;
private int _manaCostMalus;
public int SpellDamageMalus
{
get => _spellDamageMalus;
set { _spellDamageMalus = value; DoneIncrease = true; }
}
public int ManaCostMalus
{
get => _manaCostMalus;
set { _manaCostMalus = value; DoneIncrease = true; }
}
public bool DoneIncrease { get; private set; }
public PsychicAttackTimer(Mobile defender) : base(TimeSpan.FromSeconds(10))
{
_defender = defender;
_spellDamageMalus = 15;
_manaCostMalus = 15;
DoneIncrease = false;
}
protected override void OnTick()
{
RemoveEffects(_defender);
}
}
}
}

View file

@ -13,18 +13,8 @@ namespace Server.Items
public override int BaseMana => 30;
public override bool RequiresSE => true;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Bushido) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
from.SendLocalizedMessage(1070768, "50");
return false;
}
return base.CheckSkills(from);
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Bushido;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{

View file

@ -0,0 +1,46 @@
namespace Server.Items
{
public class SerpentArrow : WeaponAbility
{
public override int BaseMana => 25;
// Serpent arrow is used only as secondary ability on "Elven composite longbow" so needed skill is always 60, but for sure we will use GetRequiredSecondarySkill
public override bool RequiresSecondarySkill(Mobile from) => true;
public override double GetRequiredSecondarySkill(Mobile from) => 60;
public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Poisoning;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
ClearCurrentAbility(attacker);
defender.SendLocalizedMessage(1112369); // You have been poisoned by a lethal arrow!
int level;
if (attacker.InRange(defender, 2))
{
level = attacker.Skills.Poisoning.Fixed / 2 switch
{
>= 1000 => 3,
> 850 => 2,
> 650 => 1,
_ => 0
};
}
else
{
level = 0;
}
defender.ApplyPoison(attacker, Poison.GetPoison(level));
defender.FixedParticles(0x374A, 10, 15, 5021, EffectLayer.Waist);
defender.PlaySound(0x474);
}
}
}

View file

@ -12,6 +12,10 @@ namespace Server.Items
public override bool RequiresTactics(Mobile from) => false;
public override bool RequiresSecondarySkill(Mobile from) => true;
public override double GetRequiredSecondarySkill(Mobile from) => 80;
public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Stealth;
public override bool CheckSkills(Mobile from)
{
if (!base.CheckSkills(from))

View file

@ -13,17 +13,9 @@ namespace Server.Items
public override int BaseMana => 30;
public override double DamageScalar => 1.2;
public override bool CheckSkills(Mobile from)
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
{
// You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063352, "50");
return false;
}
public override bool RequiresSecondarySkill(Mobile from) => true;
public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Ninjitsu;
return base.CheckSkills(from);
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{

View file

@ -38,12 +38,12 @@ namespace Server.Items
new DualWield(),
new DoubleShot(),
new ArmorPierce(),
null,
null,
null,
null,
null,
null,
new Bladeweave(),
new ForceArrow(),
new LightningArrow(),
new PsychicAttack(),
new SerpentArrow(),
new ForceOfNature(),
new Disrobe()
};
@ -109,16 +109,60 @@ namespace Server.Items
public virtual bool RequiresTactics(Mobile from) => true;
public virtual bool RequiresSecondarySkill(Mobile from) => false;
/// <summary>
/// Return primary skill for ability. Default 70/90
/// </summary>
/// <param name="from"></param>
/// <returns></returns>
public virtual double GetRequiredSkill(Mobile from)
{
if (from.Weapon is BaseWeapon weapon)
{
if (weapon.PrimaryAbility == this)
if (weapon.PrimaryAbility == this || weapon.PrimaryAbility == Bladeweave)
{
return 70.0;
}
if (weapon.SecondaryAbility == this)
if (weapon.SecondaryAbility == this || weapon.SecondaryAbility == Bladeweave)
{
return 90.0;
}
}
return 200.0;
}
/// <summary>
/// Returns secondary skill for Ability. Default 50.
/// </summary>
/// <param name="from"></param>
/// <returns></returns>
public virtual double GetRequiredSecondarySkill(Mobile from) => 50.0;
/// <summary>
/// Return secondary skillName. Default bushido/ninjitsu
/// </summary>
/// <param name="from"></param>
/// <returns></returns>
public virtual SkillName GetSecondarySkillName(Mobile from) => from.Skills[SkillName.Ninjitsu].Base > from.Skills[SkillName.Bushido].Base ? SkillName.Ninjitsu : SkillName.Bushido;
/// <summary>
/// Returns tactics needed for Ability. Default 70/90.
/// </summary>
/// <param name="from"></param>
/// <returns></returns>
public virtual double GetRequiredTactics(Mobile from)
{
if (from.Weapon is BaseWeapon weapon)
{
if (weapon.PrimaryAbility == this || weapon.PrimaryAbility == Bladeweave)
{
return 70.0;
}
if (weapon.SecondaryAbility == this || weapon.SecondaryAbility == Bladeweave)
{
return 90.0;
}
@ -184,13 +228,32 @@ namespace Server.Items
var reqSkill = GetRequiredSkill(from);
var reqTactics = Core.ML && RequiresTactics(from);
if (Core.ML && reqTactics && from.Skills.Tactics.Base < reqSkill)
if (reqTactics && from.Skills.Tactics.Base < GetRequiredTactics(from))
{
// You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
from.SendLocalizedMessage(1079308, reqSkill.ToString());
return false;
}
var reqSecondarySkill = GetRequiredSecondarySkill(from);
SkillName secondarySkill = GetSecondarySkillName(from);
if (RequiresSecondarySkill(from) && from.Skills[secondarySkill].Base < reqSecondarySkill)
{
int loc = GetSkillLocalization(secondarySkill);
if (loc == 1060184)
{
from.SendLocalizedMessage(loc);
}
else
{
from.SendLocalizedMessage(loc, reqSecondarySkill.ToString());
}
return false;
}
if (skill?.Base >= reqSkill)
{
return true;
@ -207,19 +270,22 @@ namespace Server.Items
}
/* </UBWS> */
if (reqTactics)
{
// You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
from.SendLocalizedMessage(1079308, reqSkill.ToString());
}
else
{
// You need ~1_SKILL_REQUIREMENT~ weapon skill to perform that attack
from.SendLocalizedMessage(1060182, reqSkill.ToString());
}
// You need ~1_SKILL_REQUIREMENT~ weapon skill to perform that attack
from.SendLocalizedMessage(1060182, reqSkill.ToString());
return false;
}
private int GetSkillLocalization(SkillName skill)
{
return skill switch
{
SkillName.Bushido => 1063347, // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
SkillName.Ninjitsu => 1063347, // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
SkillName.Poisoning => 1060184, // You lack the required poisoning to perform that attack
_ => 1157351 // You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
};
}
public virtual bool CheckSkills(Mobile from) => CheckWeaponSkill(from);
@ -420,10 +486,7 @@ namespace Server.Items
{
private readonly Mobile m_Mobile;
public WeaponAbilityTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0))
{
m_Mobile = from;
}
public WeaponAbilityTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0)) => m_Mobile = from;
protected override void OnTick()
{

View file

@ -28,7 +28,7 @@ namespace Server.Items
return;
}
if (!(attacker.Weapon is BaseWeapon weapon))
if (attacker.Weapon is not BaseWeapon weapon)
{
return;
}

View file

@ -1024,6 +1024,7 @@ namespace Server.Items
}
ImmolatingWeaponSpell.StopImmolating(this);
ForceOfNature.Remove(m);
m.CheckStatTimers();
@ -1157,6 +1158,13 @@ namespace Server.Items
bonus = AosAttributes.GetValue(defender, AosAttribute.DefendChance);
var info = ForceArrow.GetInfo(attacker, defender);
if (info != null && info.Defender == defender)
{
bonus -= info.DefenseChanceMalus;
}
if (DivineFurySpell.UnderEffect(defender))
{
bonus -= 20; // defender loses 20% bonus when they're under divine fury
@ -1662,6 +1670,8 @@ namespace Server.Items
percentageBonus += (int)(move.GetDamageScalar(attacker, defender) * 100) - 100;
}
percentageBonus += (int)(ForceOfNature.GetDamageScalar(attacker, defender) * 100) - 100;
percentageBonus += (int)(damageBonus * 100) - 100;
var cs = CheckSlayers(attacker, defender);
@ -1864,7 +1874,10 @@ namespace Server.Items
move = null;
}
var ignoreArmor = a is ArmorIgnore || move?.IgnoreArmor(attacker) == true;
var ignoreArmor = a is ArmorIgnore ||
move?.IgnoreArmor(attacker) == true ||
Bladeweave.BladeWeaving(attacker, out var bladeweavingAbi) &&
bladeweavingAbi is ArmorIgnore;
var damageGiven = AOS.Damage(
defender,
@ -1981,8 +1994,8 @@ namespace Server.Items
mobile.LocalOverheadMessage(
MessageType.Regular,
0x3B2,
1061121
); // Your equipment is severely damaged.
1061121 // Your equipment is severely damaged.
);
}
}
else
@ -2101,6 +2114,8 @@ namespace Server.Items
a?.OnHit(attacker, defender, damage);
move?.OnHit(attacker, defender, damage);
ForceOfNature.OnHit(attacker, defender);
if (defender is IHonorTarget it)
{
it.ReceivedHonorContext?.OnTargetHit(attacker);
@ -2142,6 +2157,11 @@ namespace Server.Items
// SDI bonus
damageBonus += AosAttributes.GetValue(attacker, AosAttribute.SpellDamage);
if(PsychicAttack.Registry.TryGetValue(attacker,out var timer))
{
damageBonus -= timer.SpellDamageMalus;
}
var context = TransformationSpellHelper.GetContext(attacker);
if (context?.Spell is ReaperFormSpell spell)

View file

@ -8,7 +8,7 @@ namespace Server.Items
public GnarledStaff() : base(0x13F8) => Weight = 3.0;
public override WeaponAbility PrimaryAbility => WeaponAbility.ConcussionBlow;
public override WeaponAbility SecondaryAbility => WeaponAbility.ParalyzingBlow;
public override WeaponAbility SecondaryAbility => WeaponAbility.ForceOfNature;
public override int AosStrengthReq => 20;
public override int AosMinDamage => 15;

View file

@ -228,57 +228,190 @@ namespace Server
{
DismountPrevention = 0x3E9,
NoRearm = 0x3EA,
// Currently, no 0x3EB or 0x3EC
NightSight = 0x3ED, // *
//Currently, no 0x3EB or 0x3EC
NightSight = 0x3ED, //*
DeathStrike,
EvilOmen,
UnknownStandingSwirl, // Which is healing throttle & Stamina throttle?
UnknownKneelingSword,
DivineFury, // *
EnemyOfOne, // *
HidingAndOrStealth, // *
ActiveMeditation, // *
BloodOathCaster, // *
BloodOathCurse, // *
CorpseSkin, // *
Mindrot, // *
PainSpike, // *
HonoredDebuff,
AchievePerfection,
DivineFury, //*
EnemyOfOne, //*
HidingAndOrStealth, //*
ActiveMeditation, //*
BloodOathCaster, //*
BloodOathCurse, //*
CorpseSkin, //*
Mindrot, //*
PainSpike, //*
Strangle,
GiftOfRenewal, // *
AttuneWeapon, // *
Thunderstorm, // *
EssenceOfWind, // *
EtherealVoyage, // *
GiftOfLife, // *
ArcaneEmpowerment, // *
GiftOfRenewal, //*
AttuneWeapon, //*
Thunderstorm, //*
EssenceOfWind, //*
EtherealVoyage, //*
GiftOfLife, //*
ArcaneEmpowerment, //*
MortalStrike,
ReactiveArmor, // *
Protection, // *
ReactiveArmor, //*
Protection, //*
ArchProtection,
MagicReflection, // *
Incognito, // *
MagicReflection, //*
Incognito, //*
Disguised,
AnimalForm,
Polymorph,
Invisibility, // *
Paralyze, // *
Invisibility, //*
Paralyze, //*
Poison,
Bleed,
Clumsy, // *
FeebleMind, // *
Weaken, // *
Curse, // *
Clumsy, //*
FeebleMind, //*
Weaken, //*
Curse, //*
MassCurse,
Agility, // *
Cunning, // *
Strength, // *
Bless, // *
Agility, //*
Cunning, //*
Strength, //*
Bless, //*
Sleep,
StoneForm,
SpellPlague,
SpellTrigger,
NetherBolt,
Fly
Berserk,
MassSleep,
Fly,
Inspire,
Invigorate,
Resilience,
Perseverance,
TribulationTarget,
DespairTarget,
FishPie = 0x426,
HitLowerAttack,
HitLowerDefense,
DualWield,
Block,
DefenseMastery,
DespairCaster,
Healing,
SpellFocusingBuff,
SpellFocusingDebuff,
RageFocusingDebuff,
RageFocusingBuff,
Warding,
TribulationCaster,
ForceArrow,
Disarm,
Surge,
Feint,
TalonStrike,
PsychicAttack,
ConsecrateWeapon,
GrapesOfWrath,
EnemyOfOneDebuff,
HorrificBeast,
LichForm,
VampiricEmbrace,
CurseWeapon,
ReaperForm,
ImmolatingWeapon,
Enchant,
HonorableExecution,
Confidence,
Evasion,
CounterAttack,
LightningStrike,
MomentumStrike,
OrangePetals,
RoseOfTrinsic,
PoisonImmunity,
Veterinary,
Perfection,
Honored,
ManaPhase,
FanDancerFanFire,
Rage,
Webbing,
MedusaStone,
TrueFear,
AuraOfNausea,
HowlOfCacophony,
GazeDespair,
HiryuPhysicalResistance,
RuneBeetleCorruption,
BloodwormAnemia,
RotwormBloodDisease,
SkillUseDelay,
FactionStatLoss,
HeatOfBattleStatus,
CriminalStatus,
ArmorPierce,
SplinteringEffect,
SwingSpeedDebuff,
WraithForm,
CityTradeDeal = 0x466,
HumilityDebuff = 0x467,
Spirituality,
Humility,
// Skill Masteries
Rampage,
Stagger, // Debuff
Toughness,
Thrust,
Pierce, // Debuff
PlayingTheOdds,
FocusedEye,
Onslaught, // Debuff
ElementalFury,
ElementalFuryDebuff, // Debuff
CalledShot,
Knockout,
SavingThrow,
Conduit,
EtherealBurst,
MysticWeapon,
ManaShield,
AnticipateHit,
Warcry,
Shadow,
WhiteTigerForm,
Bodyguard,
HeightenedSenses,
Tolerance,
DeathRay,
DeathRayDebuff,
Intuition,
EnchantedSummoning,
ShieldBash,
Whispering,
CombatTraining,
InjectedStrikeDebuff,
InjectedStrike,
UnknownTomato,
PlayingTheOddsDebuff,
DragonTurtleDebuff,
Boarding,
Potency,
ThrustDebuff,
FistsOfFury, // 1169
BarrabHemolymphConcentrate,
JukariBurnPoiltice,
KurakAmbushersEssence,
BarakoDraftOfMight,
UraliTranceTonic,
SakkhraProphylaxis, // 1175
Sparks,
Swarm,
BoneBreaker,
Unknown2,
SwarmImmune,
BoneBreakerImmune,
UnknownGoblin,
UnknownRedDrop,
UnknownStar,
FeintDebuff,
CaddelliteInfused,
PotionGloriousFortune,
MysticalPolymorphTotem,
UnknownDebuff,
}
}