Merge pull request #38 from RebirthUO/feat/issue-32-nether-bolt
This commit is contained in:
commit
752bf3cc29
4 changed files with 281 additions and 2 deletions
|
|
@ -0,0 +1,205 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Server.Items;
|
||||
using Server.Spells;
|
||||
using Server.Spells.First;
|
||||
using Server.Spells.Mysticism;
|
||||
using Xunit;
|
||||
|
||||
namespace Server.Tests.Spells.Mysticism;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class NetherBoltSpellTests
|
||||
{
|
||||
private static Mobile NewMobile()
|
||||
{
|
||||
var m = new Mobile(World.NewMobile);
|
||||
m.DefaultMobileInit();
|
||||
m.InitStats(100, 100, 100);
|
||||
m.Mana = 100;
|
||||
return m;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpellMetadata_MatchesFirstCircleMysticismSources()
|
||||
{
|
||||
var caster = NewMobile();
|
||||
var spell = new NetherBoltSpell(caster);
|
||||
|
||||
Assert.Equal("Nether Bolt", spell.Name);
|
||||
Assert.Equal("In Corp Ylem", spell.Mantra);
|
||||
Assert.Equal(SpellCircle.First, spell.Circle);
|
||||
Assert.Equal(TimeSpan.FromSeconds(0.5), spell.CastDelayBase);
|
||||
Assert.Equal(4, spell.GetMana());
|
||||
Assert.Equal(0.0, spell.RequiredSkill);
|
||||
Assert.Equal(SkillName.Mysticism, spell.CastSkill);
|
||||
Assert.Equal([Reagent.BlackPearl, Reagent.SulfurousAsh], spell.Reagents);
|
||||
Assert.True(spell.DelayedDamage);
|
||||
|
||||
caster.Delete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MysticSpellbookAndScroll_UseNetherBoltSlot()
|
||||
{
|
||||
var book = new MysticSpellbook(1);
|
||||
var scroll = new NetherBoltScroll();
|
||||
|
||||
Assert.Equal(677, book.BookOffset);
|
||||
Assert.Equal(16, book.BookCount);
|
||||
Assert.True(book.HasSpell(677));
|
||||
Assert.Equal(677, GetSpellScrollId(scroll));
|
||||
|
||||
book.Delete();
|
||||
scroll.Delete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterMysticism_PreSa_DoesNotExposeNetherBolt()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var caster = NewMobile();
|
||||
|
||||
try
|
||||
{
|
||||
ResetSpellRegistry();
|
||||
Core.Expansion = Expansion.ML;
|
||||
|
||||
Initializer.Configure();
|
||||
|
||||
Assert.Null(SpellRegistry.NewSpell(677, caster, null));
|
||||
Assert.Equal(-1, SpellRegistry.GetRegistryNumber(typeof(NetherBoltSpell)));
|
||||
Assert.IsType<MagicArrowSpell>(SpellRegistry.NewSpell(4, caster, null));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
ResetSpellRegistry();
|
||||
Initializer.Configure();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterMysticism_Sa_ExposesNetherBoltAtSpellId677()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var caster = NewMobile();
|
||||
|
||||
try
|
||||
{
|
||||
ResetSpellRegistry();
|
||||
Core.Expansion = Expansion.SA;
|
||||
|
||||
Initializer.Configure();
|
||||
|
||||
Assert.Same(typeof(NetherBoltSpell), SpellRegistry.Types[677]);
|
||||
Assert.Equal(677, SpellRegistry.GetRegistryNumber(typeof(NetherBoltSpell)));
|
||||
Assert.IsType<NetherBoltSpell>(SpellRegistry.NewSpell(677, caster, null));
|
||||
Assert.IsType<EagleStrikeSpell>(SpellRegistry.NewSpell(682, caster, null));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
ResetSpellRegistry();
|
||||
Initializer.Configure();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DelayedDamageFamily_PreventsNetherBoltAndMagicArrowStackingInEitherOrder()
|
||||
{
|
||||
var caster = NewMobile();
|
||||
var target = NewMobile();
|
||||
var netherBolt = new NetherBoltSpell(caster);
|
||||
var magicArrow = new MagicArrowSpell(caster);
|
||||
|
||||
netherBolt.StartDelayedDamageContext(target, new TestTimer());
|
||||
|
||||
Assert.True(netherBolt.HasDelayedDamageContext(target));
|
||||
Assert.True(magicArrow.HasDelayedDamageContext(target));
|
||||
|
||||
magicArrow.RemoveDelayedDamageContext(target);
|
||||
magicArrow.StartDelayedDamageContext(target, new TestTimer());
|
||||
|
||||
Assert.True(magicArrow.HasDelayedDamageContext(target));
|
||||
Assert.True(netherBolt.HasDelayedDamageContext(target));
|
||||
|
||||
netherBolt.RemoveDelayedDamageContext(target);
|
||||
caster.Delete();
|
||||
target.Delete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetNewAosDamage_UsesHigherFocusOrImbuingSupportSkill()
|
||||
{
|
||||
var focusCaster = NewMobile();
|
||||
var imbuingCaster = NewMobile();
|
||||
var lowSupportCaster = NewMobile();
|
||||
var target = NewMobile();
|
||||
|
||||
focusCaster.Skills.Mysticism.Base = 120.0;
|
||||
focusCaster.Skills.Focus.Base = 120.0;
|
||||
focusCaster.Skills.Imbuing.Base = 0.0;
|
||||
|
||||
imbuingCaster.Skills.Mysticism.Base = 120.0;
|
||||
imbuingCaster.Skills.Focus.Base = 0.0;
|
||||
imbuingCaster.Skills.Imbuing.Base = 120.0;
|
||||
|
||||
lowSupportCaster.Skills.Mysticism.Base = 120.0;
|
||||
lowSupportCaster.Skills.Focus.Base = 0.0;
|
||||
lowSupportCaster.Skills.Imbuing.Base = 0.0;
|
||||
|
||||
using var random = new PredictableRandom(0);
|
||||
|
||||
var focusDamage = new NetherBoltSpell(focusCaster).GetNewAosDamage(10, 1, 4, target);
|
||||
var imbuingDamage = new NetherBoltSpell(imbuingCaster).GetNewAosDamage(10, 1, 4, target);
|
||||
var lowSupportDamage = new NetherBoltSpell(lowSupportCaster).GetNewAosDamage(10, 1, 4, target);
|
||||
|
||||
Assert.Equal(focusDamage, imbuingDamage);
|
||||
Assert.True(focusDamage > lowSupportDamage);
|
||||
|
||||
focusCaster.Delete();
|
||||
imbuingCaster.Delete();
|
||||
lowSupportCaster.Delete();
|
||||
target.Delete();
|
||||
}
|
||||
|
||||
private static int GetSpellScrollId(SpellScroll scroll)
|
||||
{
|
||||
var field = typeof(SpellScroll).GetField("_spellID", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
return (int)field!.GetValue(scroll)!;
|
||||
}
|
||||
|
||||
private static void ResetSpellRegistry()
|
||||
{
|
||||
var types = (Type[])typeof(SpellRegistry)
|
||||
.GetField("m_Types", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.GetValue(null)!;
|
||||
Array.Clear(types);
|
||||
|
||||
var idsFromTypes = (Dictionary<Type, int>)typeof(SpellRegistry)
|
||||
.GetField("m_IDsFromTypes", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.GetValue(null)!;
|
||||
idsFromTypes.Clear();
|
||||
|
||||
typeof(SpellRegistry)
|
||||
.GetField("m_Count", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.SetValue(null, 0);
|
||||
|
||||
SpellRegistry.SpecialMoves.Clear();
|
||||
}
|
||||
|
||||
private sealed class TestTimer : Timer
|
||||
{
|
||||
public TestTimer() : base(TimeSpan.FromMinutes(5))
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using Server.Spells.Mysticism;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Spells.First
|
||||
|
|
@ -19,7 +20,9 @@ namespace Server.Spells.First
|
|||
|
||||
public override SpellCircle Circle => SpellCircle.First;
|
||||
|
||||
public override Type[] DelayedDamageSpellFamilyStacking => AOSNoDelayedDamageStackingSelf;
|
||||
private static readonly Type[] _delayedDamageSpellFamilyStacking = Core.AOS ? [typeof(NetherBoltSpell)] : null;
|
||||
|
||||
public override Type[] DelayedDamageSpellFamilyStacking => _delayedDamageSpellFamilyStacking;
|
||||
|
||||
public override bool DelayedDamage => true;
|
||||
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ namespace Server.Spells
|
|||
if (Core.SA)
|
||||
{
|
||||
// Mysticism spells
|
||||
// Register(677, typeof(NetherBoltSpell));
|
||||
Register(677, typeof(NetherBoltSpell));
|
||||
// Register(678, typeof(HealingStoneSpell));
|
||||
// Register(679, typeof(PurgeMagicSpell));
|
||||
// Register(680, typeof(EnchantSpell));
|
||||
|
|
|
|||
71
Projects/UOContent/Spells/Mysticism/NetherBoltSpell.cs
Normal file
71
Projects/UOContent/Spells/Mysticism/NetherBoltSpell.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using Server.Spells.First;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Spells.Mysticism;
|
||||
|
||||
public class NetherBoltSpell : MysticSpell, ITargetingSpell<Mobile>
|
||||
{
|
||||
private static readonly SpellInfo _info = new(
|
||||
"Nether Bolt",
|
||||
"In Corp Ylem",
|
||||
-1,
|
||||
9002,
|
||||
Reagent.BlackPearl,
|
||||
Reagent.SulfurousAsh
|
||||
);
|
||||
|
||||
private static readonly Type[] _delayedDamageSpellFamilyStacking = Core.AOS ? [typeof(MagicArrowSpell)] : null;
|
||||
|
||||
public NetherBoltSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
|
||||
{
|
||||
}
|
||||
|
||||
public override SpellCircle Circle => SpellCircle.First;
|
||||
|
||||
public override bool DelayedDamage => true;
|
||||
|
||||
public override Type[] DelayedDamageSpellFamilyStacking => _delayedDamageSpellFamilyStacking;
|
||||
|
||||
public void Target(Mobile m)
|
||||
{
|
||||
if (CheckHSequence(m))
|
||||
{
|
||||
var source = Caster;
|
||||
|
||||
SpellHelper.Turn(source, m);
|
||||
|
||||
if (Core.SA && HasDelayedDamageContext(m))
|
||||
{
|
||||
DoHurtFizzle();
|
||||
return;
|
||||
}
|
||||
|
||||
SpellHelper.CheckReflect((int)Circle, ref source, ref m);
|
||||
|
||||
source.MovingParticles(
|
||||
m,
|
||||
0x36D4,
|
||||
5,
|
||||
0,
|
||||
false,
|
||||
true,
|
||||
0x49A,
|
||||
0,
|
||||
9502,
|
||||
1,
|
||||
0,
|
||||
EffectLayer.Head,
|
||||
0x100
|
||||
);
|
||||
source.PlaySound(0x211);
|
||||
|
||||
SpellHelper.Damage(this, m, GetNewAosDamage(10, 1, 4, m), 0, 0, 0, 0, 0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCast()
|
||||
{
|
||||
Caster.Target = new SpellTarget<Mobile>(this, TargetFlags.Harmful);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue