fix: Adds throwing weapons (#1107)

This commit is contained in:
Kamron Batman 2022-07-01 17:03:27 -07:00 committed by GitHub
parent 38686c09b2
commit 7c981b2d10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 443 additions and 160 deletions

View file

@ -23,7 +23,7 @@ public static class Utility
private static Dictionary<IPAddress, IPAddress> _ipAddressTable;
private static readonly SkillName[] m_AllSkills =
private static SkillName[] _allSkills =
{
SkillName.Alchemy,
SkillName.Anatomy,
@ -79,7 +79,10 @@ public static class Utility
SkillName.Chivalry,
SkillName.Bushido,
SkillName.Ninjitsu,
SkillName.Spellweaving
SkillName.Spellweaving,
// SkillName.Mysticism,
// SkillName.Imbuing,
SkillName.Throwing
};
private static readonly SkillName[] m_CombatSkills =
@ -622,11 +625,7 @@ public static class Utility
array.Length > 0 ? array.GetValue(Math.Clamp(index, 0, array.Length - 1)) : emptyValue;
public static SkillName RandomSkill() =>
m_AllSkills[Random(
m_AllSkills.Length - (Core.ML ? 0 :
Core.SE ? 1 :
Core.AOS ? 3 : 6)
)];
_allSkills[Random(_allSkills.Length - (Core.ML ? 0 : Core.SE ? 1 : Core.AOS ? 3 : 6))];
public static SkillName RandomCombatSkill() => m_CombatSkills.RandomElement();

View file

@ -48,11 +48,12 @@ namespace Server.Items
public override void OnDoubleClick(Mobile from)
{
if ((_arrows > 0 || _bolts > 0) && from.InRange(GetWorldLocation(), 1))
var inRange = from.InRange(GetWorldLocation(), 1);
if ((_arrows > 0 || _bolts > 0) && inRange)
{
Gather(from);
}
else
else if (from.Weapon is not BaseThrown || inRange)
{
Fire(from);
}

View file

@ -2,114 +2,108 @@ using System;
using Server.Mobiles;
using Server.Spells.Ninjitsu;
namespace Server.Items
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
{
/// <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 void OnHit(Mobile attacker, Mobile defender, int damage)
{
public static readonly TimeSpan RemountDelay = TimeSpan.FromSeconds(10.0);
public override int BaseMana => 20;
public override bool Validate(Mobile from)
if (!Validate(attacker) || !CheckMana(attacker, true))
{
if (!base.Validate(from))
{
return false;
}
if (from.Mounted && from.Weapon is not Lance)
{
from.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted!
return false;
}
return true;
return;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
if (defender is ChaosDragoon or ChaosDragoonElite)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
return;
}
if (attacker.Mounted || attacker.Flying)
{
if (attacker.Weapon is not Lance || !defender.Mounted && !defender.Flying && defender.Weapon is not Lance)
{
return;
attacker.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted!
}
}
ClearCurrentAbility(attacker);
var mount = defender.Mount;
if (mount == null && !AnimalForm.UnderTransformation(defender))
{
attacker.SendLocalizedMessage(1060848); // This attack only works on mounted targets
return;
}
if (Core.ML && attacker is LesserHiryu && Utility.RandomDouble() <= 0.8)
{
return; // Lesser Hiryu have an 80% chance of missing this attack
}
DoDismount(attacker, defender, TimeSpan.FromSeconds(10));
if (!attacker.Mounted)
{
AOS.Damage(defender, attacker, Utility.RandomMinMax(15, 25), 100, 0, 0, 0, 0);
}
}
public static void DoDismount(Mobile attacker, Mobile defender, TimeSpan delay, BlockMountType type = BlockMountType.Dazed)
{
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 (defender.Flying)
{
defender.SendLocalizedMessage(1113590, attacker.Name); // You have been grounded by ~1_NAME~!
}
else if (mobile.Mounted)
{
mobile.SendLocalizedMessage(1040023); // You have been knocked off of your mount!
}
if (defender is ChaosDragoon or ChaosDragoonElite)
{
return;
}
mobile.SetMountBlock(type, delay, true);
}
else
{
defender.Mount.Rider = null;
}
// TODO: Should there be a message here?
if (attacker.Mounted && (attacker.Weapon is not Lance || defender.Weapon is not Lance))
{
return;
}
ClearCurrentAbility(attacker);
var mount = defender.Mount;
if (mount == null && !AnimalForm.UnderTransformation(defender))
{
attacker.SendLocalizedMessage(1060848); // This attack only works on mounted targets
return;
}
if (Core.ML && attacker is LesserHiryu && Utility.RandomDouble() <= 0.8)
{
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 { ControlMaster: PlayerMobile pm })
{
pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
}
if (!attacker.Mounted)
{
AOS.Damage(defender, attacker, Utility.RandomMinMax(15, 25), 100, 0, 0, 0, 0);
}
if (attacker is PlayerMobile playerMobile)
{
playerMobile.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, true);
}
else if (Core.ML && attacker is BaseCreature { ControlMaster: PlayerMobile pm })
{
pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
}
}
}

View file

@ -1,49 +0,0 @@
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) || !CheckMana(attacker, true))
{
return;
}
ClearCurrentAbility(attacker);
var toDisrobe = defender.FindItemOnLayer(Layer.InnerTorso);
if (toDisrobe?.Movable == false)
{
toDisrobe = defender.FindItemOnLayer(Layer.OuterTorso);
}
var pack = defender.Backpack;
if (pack == null || toDisrobe?.Movable == false)
{
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
}
else
{
// 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,45 @@
using Server.Mobiles;
using System;
namespace Server.Items;
// The projectile will be infused with energy by the attacker causing it to do more damage and stun or dismount the target.
// The player infuses their throwing projectile with mystical power.
// The infused projectile will dismount the target if possible; otherwise it will temporarily stun the target.
// The target will be hit with chaos damage regardless of whether they were dismounted or paralyzed.
public class InfusedThrow : WeaponAbility
{
public override int BaseMana => 25;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!CheckMana(attacker, true))
{
return;
}
ClearCurrentAbility(attacker);
attacker.SendLocalizedMessage(1149563); //The infused projectile strikes a target!
defender.SendLocalizedMessage(1149564); //You are struck by the infused projectile and take damage!
AOS.Damage(defender, attacker, 15, false, 0, 0, 0, 0, 0, 100);
IMount mount = defender.Mount;
if ((defender.Mounted || defender.Flying || Spells.Ninjitsu.AnimalForm.UnderTransformation(defender)) &&
!attacker.Mounted && !attacker.Flying && defender is not ChaosDragoon && defender is not ChaosDragoonElite)
{
defender.PlaySound(0x140);
defender.FixedParticles(0x3728, 10, 15, 9955, EffectLayer.Waist);
Items.Dismount.DoDismount(attacker, defender, TimeSpan.FromSeconds(10));
}
else
{
defender.FixedEffect(0x376A, 9, 32);
defender.PlaySound(0x204);
defender.Paralyze(TimeSpan.FromSeconds(defender.Player ? 3.0 : 6.0));
}
}
}

View file

@ -0,0 +1,91 @@
using System;
using Server.Collections;
namespace Server.Items;
// The thrown projectile will arc to a second target after hitting the primary target.
// Chaos energy will burst from the projectile at each target. This will only hit targets that are in combat with the user.
public class MysticArc : WeaponAbility
{
private readonly int Damage = 15;
private Mobile _target;
private Mobile _mobile;
public override int BaseMana => 20;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!CheckMana(attacker, true) && defender != null)
{
return;
}
BaseThrown weapon = attacker.Weapon as BaseThrown;
if (weapon == null)
{
return;
}
using var queue = PooledRefQueue<Mobile>.Create();
IPooledEnumerable eable = attacker.GetMobilesInRange(weapon.MaxRange);
foreach (Mobile m in eable)
{
if (m == defender)
{
continue;
}
if (m.Combatant != attacker)
{
continue;
}
queue.Enqueue(m);
}
eable.Free();
if (queue.Count > 0)
{
_target = queue.PeekRandom();
}
AOS.Damage(defender, attacker, Damage, 0, 0, 0, 0, 0, 100);
if (_target != null)
{
defender?.MovingEffect(_target, weapon.ItemID, 18, 1, false, false);
Timer.DelayCall(TimeSpan.FromSeconds(0.3), ThrowAgain);
_mobile = attacker;
}
ClearCurrentAbility(attacker);
}
public void ThrowAgain()
{
if (_target == null || _mobile == null)
{
return;
}
if (_mobile.Weapon is not BaseThrown weapon)
{
return;
}
if (GetCurrentAbility(_mobile) is MysticArc)
{
ClearCurrentAbility(_mobile);
}
if (weapon.CheckHit(_mobile, _target))
{
weapon.OnHit(_mobile, _target, 0.0);
AOS.Damage(_target, _mobile, Damage, 0, 0, 0, 0, 100);
}
}
}

View file

@ -44,7 +44,8 @@ namespace Server.Items
new PsychicAttack(),
new SerpentArrow(),
new ForceOfNature(),
new Disrobe()
new InfusedThrow(),
new MysticArc(),
};
public static readonly WeaponAbility ArmorIgnore = Abilities[1];
@ -79,7 +80,8 @@ namespace Server.Items
public static readonly WeaponAbility SerpentArrow = Abilities[28];
public static readonly WeaponAbility ForceOfNature = Abilities[29];
public static readonly WeaponAbility Disrobe = Abilities[30];
public static readonly WeaponAbility InfusedThrow = Abilities[30];
public static readonly WeaponAbility MysticArc = Abilities[31];
private static readonly Dictionary<Mobile, WeaponAbilityContext> m_PlayersTable =
new();

View file

@ -0,0 +1,105 @@
using System;
using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0)]
public abstract partial class BaseThrown : BaseRanged
{
public BaseThrown(int itemID) : base(itemID)
{
}
public abstract int MinThrowRange { get; }
public virtual int MaxThrowRange => MinThrowRange + 3;
public override int DefMaxRange
{
get
{
int baseRange = MaxThrowRange;
return Parent is Mobile attacker
? baseRange - 3 + (attacker.Str - AosStrengthReq) / ((140 - AosStrengthReq) / 3)
: baseRange;
}
}
public override int EffectID => ItemID;
public override Type AmmoType => null;
public override Item Ammo => null;
public override int DefHitSound => 0x5D3;
public override int DefMissSound => 0x5D4;
public override SkillName DefSkill => SkillName.Throwing;
public override WeaponAnimation DefAnimation => WeaponAnimation.Throwing;
public override bool OnFired(Mobile attacker, Mobile defender)
{
if (!attacker.InRange(defender, 1))
{
attacker.MovingEffect(defender, EffectID, 18, 1, false, false, Hue, 0);
}
return true;
}
public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1)
{
if (WeaponAbility.GetCurrentAbility(attacker) is not MysticArc)
{
var location = new WorldLocation(defender.Location, attacker.Map);
Timer.StartTimer(TimeSpan.FromSeconds(0.3), () => Return(attacker, defender, location));
}
base.OnHit(attacker, defender, damageBonus);
}
public override void OnMiss(Mobile attacker, Mobile defender)
{
if (WeaponAbility.GetCurrentAbility(attacker) is not MysticArc)
{
var location = new WorldLocation(defender.Location, attacker.Map);
Timer.StartTimer(TimeSpan.FromSeconds(0.3), () => Return(attacker, defender, location));
}
base.OnMiss(attacker, defender);
}
public virtual void Return(Mobile thrower, Mobile target, WorldLocation worldLocation)
{
if (thrower == null)
{
return;
}
if (target != null)
{
target.MovingEffect(thrower, EffectID, 18, 1, false, false, Hue, 0);
}
else
{
Effects.SendMovingParticles(
new Entity(Serial.Zero, worldLocation.Location, worldLocation.Map),
thrower,
ItemID,
18,
0,
false,
false,
Hue,
0,
9502,
1,
0,
(EffectLayer)255,
0x100
);
}
}
}

View file

@ -0,0 +1,25 @@
using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0)]
public partial class Boomerang : BaseThrown
{
[Constructible]
public Boomerang() : base(0x8FF)
{
Weight = 4.0;
Layer = Layer.OneHanded;
}
public override int MinThrowRange => 4;
public override WeaponAbility PrimaryAbility => WeaponAbility.MysticArc;
public override WeaponAbility SecondaryAbility => WeaponAbility.ConcussionBlow;
public override int AosStrengthReq => 25;
public override int AosMinDamage => 11;
public override int AosMaxDamage => 15;
public override float MlSpeed => 2.75f;
public override int InitMinHits => 31;
public override int InitMaxHits => 60;
}

View file

@ -0,0 +1,27 @@
using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0)]
public partial class Cyclone : BaseThrown
{
[Constructible]
public Cyclone() : base(0x901)
{
Weight = 6.0;
Layer = Layer.OneHanded;
}
public override int MinThrowRange => 6;
public override WeaponAbility PrimaryAbility => WeaponAbility.MovingShot;
public override WeaponAbility SecondaryAbility => WeaponAbility.InfusedThrow;
public override int AosStrengthReq => 40;
public override int AosMinDamage => 13;
public override int AosMaxDamage => 17;
public override float MlSpeed => 3.25f;
public override int InitMinHits => 31;
public override int InitMaxHits => 60;
}

View file

@ -0,0 +1,26 @@
using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0)]
public partial class SoulGlaive : BaseThrown
{
[Constructible]
public SoulGlaive() : base(0x090A)
{
Weight = 8.0;
Layer = Layer.OneHanded;
}
public override int MinThrowRange => 8;
public override WeaponAbility PrimaryAbility => WeaponAbility.ArmorIgnore;
public override WeaponAbility SecondaryAbility => WeaponAbility.MortalStrike;
public override int AosStrengthReq => 60;
public override int AosMinDamage => 16;
public override int AosMaxDamage => 20;
public override float MlSpeed => 4.00f;
public override int InitMinHits => 31;
public override int InitMaxHits => 65;
}

View file

@ -59,6 +59,7 @@ namespace Server.Items
Pierce2H = 14,
ShootBow = 18,
ShootXBow = 19,
Wrestle = 31
Wrestle = 31,
Throwing = 32
}
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Items.BaseThrown"
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Items.Boomerang"
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Items.Cyclone"
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Items.SoulGlaive"
}

View file

@ -1264,7 +1264,7 @@ public static class CharacterCreation
{
if (gargoyle)
{
// EquipItem(m, new Boomerang());
EquipItem(m, new Boomerang());
}
break;