Mirror Image of ninjitsu? I guess
using System;
using System.Collections.Generic;
using Server.Items;
using Server.Mobiles;
using Server.Spells;
using Server.Spells.Necromancy;
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 override TimeSpan CastDelayBase
{
get
{
return TimeSpan.FromSeconds(1.5);
}
}
public override double RequiredSkill
{
get
{
return Core.ML ? 20.0 : 40.0;
}
}
public override int RequiredMana
{
get
{
return 10;
}
}
public override bool BlockedByAnimalForm
{
get
{
return false;
}
}
public static bool HasClone(Mobile m)
{
return m_CloneCount.ContainsKey(m);
}
public static void AddClone(Mobile m)
{
if (m == null)
return;
if (m_CloneCount.ContainsKey(m))
m_CloneCount[m]++;
else
m_CloneCount[m] = 1;
}
public static void RemoveClone(Mobile m)
{
if (m == null)
return;
if (m_CloneCount.ContainsKey(m))
{
m_CloneCount[m]--;
if (m_CloneCount[m] == 0)
m_CloneCount.Remove(m);
}
}
public override bool CheckCast()
{
if (Caster.Mounted)
{
Caster.SendLocalizedMessage(1063132); // You cannot use this ability while mounted.
return false;
}
else if ((Caster.Followers + 1) > Caster.FollowersMax)
{
Caster.SendLocalizedMessage(1063133); // You cannot summon a mirror image because you have too many followers.
return false;
}
else if (TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
return false;
}
else if (Caster.Flying)
{
Caster.SendLocalizedMessage(1113415); // You cannot use this ability while flying.
return false;
}
return base.CheckCast();
}
public override bool CheckDisturb(DisturbType type, bool firstCircle, bool resistable)
{
return 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 static Clone GetDeflect(Mobile attacker, Mobile defender)
{
Clone clone = null;
if (HasClone(defender) && (defender.Skills.Ninjitsu.Value / 150.0) > Utility.RandomDouble())
{
IPooledEnumerable eable = defender.GetMobilesInRange(4);
foreach (Mobile m in eable)
{
clone = m as Clone;
if (clone != null && clone.Summoned && clone.SummonMaster == defender)
{
attacker.SendLocalizedMessage(1063141); // Your attack has been diverted to a nearby mirror image of your target!
defender.SendLocalizedMessage(1063140); // You manage to divert the attack onto one of your nearby mirror images.
break;
}
}
eable.Free();
}
return clone;
}
}
}
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)
{
m_Caster = caster;
Body = caster.Body;
Hue = caster.Hue;
Female = caster.Female;
Name = caster.Name;
NameHue = caster.NameHue;
Title = caster.Title;
Kills = caster.Kills;
HairItemID = caster.HairItemID;
HairHue = caster.HairHue;
FacialHairItemID = caster.FacialHairItemID;
FacialHairHue = caster.FacialHairHue;
for (int i = 0; i < caster.Skills.Length; ++i)
{
Skills.Base = caster.Skills.Base;
Skills.Cap = caster.Skills.Cap;
}
for (int i = 0; i < caster.Items.Count; i++)
{
AddItem(CloneItem(caster.Items));
}
Warmode = true;
Summoned = true;
SummonMaster = caster;
ControlOrder = OrderType.Follow;
ControlTarget = caster;
TimeSpan duration = TimeSpan.FromSeconds(30 + caster.Skills.Ninjitsu.Fixed / 40);
new UnsummonTimer(caster, this, duration).Start();
SummonEnd = DateTime.UtcNow + duration;
MirrorImage.AddClone(m_Caster);
IgnoreMobiles = true;
}
public Clone(Serial serial)
: base(serial)
{
}
public override bool DeleteCorpseOnDeath
{
get
{
return true;
}
}
public override bool IsDispellable
{
get
{
return false;
}
}
public override bool Commandable
{
get
{
return false;
}
}
protected override BaseAI ForcedAI
{
get
{
return new CloneAI(this);
}
}
public override bool CanDetectHidden { get { return false; } }
public override bool IsHumanInTown()
{
return false;
}
public override bool OnMoveOver(Mobile m)
{
return true;
}
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(GenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
writer.Write(m_Caster);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
m_Caster = reader.ReadMobile();
MirrorImage.AddClone(m_Caster);
}
private Item CloneItem(Item item)
{
Item newItem = new Item(item.ItemID);
newItem.Hue = item.Hue;
newItem.Layer = item.Layer;
return newItem;
}
}
}
namespace Server.Mobiles
{
public class CloneAI : BaseAI
{
public CloneAI(Clone m)
: base(m)
{
m.CurrentSpeed = m.ActiveSpeed;
}
public override bool Think()
{
// Clones only follow their owners
Mobile master = m_Mobile.SummonMaster;
if (master != null && master.Map == m_Mobile.Map && master.InRange(m_Mobile, m_Mobile.RangePerception))
{
int iCurrDist = (int)m_Mobile.GetDistanceToSqrt(master);
bool bRun = (iCurrDist > 5);
WalkMobileRange(master, 2, bRun, 0, 1);
}
else
WalkRandom(2, 2, 1);
return true;
}
}
}
You could look at normal summons.
Like a summoned elemental or a bladed spirit depending on how you want to go.
Thank you for your answer. It was a good reference! Can I ask you one more question, PyrO?using System;
using System.Collections;
using Server.Targeting;
using Server.Network;
using Server.Mobiles;
using Server.Spells;
using Server.Misc;
using Server.Items;
namespace Server.Spells.Eighth
{
public class FireballsSpell : MagerySpell
{
private static readonly SpellInfo m_Info = new SpellInfo(
"Fire balls", "Fire balls",
269,
9020,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk);
public FireballsSpell(Mobile caster, Item scroll)
: base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle
{
get
{
return SpellCircle.Eighth;
}
}
private static Hashtable m_Table = new Hashtable();
public override double RequiredSkill
{
get
{
return 120.0;
}
}
public static bool HasEffect( Mobile m )
{
return ( m_Table[m] != null );
}
public static void RemoveEffect( Mobile m )
{
Timer t = (Timer)m_Table[m];
if ( t != null )
{
t.Stop();
m_Table.Remove( m );
}
}
public override void OnCast()
{
Caster.Target = new InternalTarget( this );
}
public void Target( Mobile m )
{
if ( !Caster.CanSee( m ) )
{
Caster.SendLocalizedMessage( 500237 );
}
else if ( CheckBSequence( m, false ) )
{
SpellHelper.Turn( Caster, m );
double damage = Caster.Skills[SkillName.Magery].Value / 3.0;
Timer t = new InternalTimer( m, Caster );
t.Start();
m_Table[m] = t;
}
FinishSequence();
}
private class InternalTarget : Target
{
private FireballsSpell m_Owner;
public InternalTarget( FireballsSpell owner ) : base( 12, false, TargetFlags.Beneficial )
{
m_Owner = owner;
}
protected override void OnTarget( Mobile from, object o )
{
if ( o is Mobile )
{
m_Owner.Target( (Mobile)o );
}
}
protected override void OnTargetFinish( Mobile from )
{
m_Owner.FinishSequence();
}
}
private class InternalTimer : Timer
{
private Mobile dest, source;
private DateTime NextTick;
private DateTime Expire;
public InternalTimer( Mobile m, Mobile from ) : base( TimeSpan.FromSeconds( 0.1 ), TimeSpan.FromSeconds( 0.1 ) )
{
dest = m;
source = from;
Priority = TimerPriority.FiftyMS;
Expire = DateTime.Now + TimeSpan.FromSeconds(1.5);
}
protected override void OnTick()
{
if ( !dest.CheckAlive() )
{
Stop();
m_Table.Remove( dest );
}
if ( DateTime.Now < NextTick )
return;
if ( DateTime.Now >= NextTick )
{
double tick = source.Skills[SkillName.Magery].Value;
double damage = source.Skills[SkillName.Magery].Value / 3.0;
source.MovingParticles(dest, 0xA61B, 7, 0, false, true, 9502, 4019, 0x160);
dest.PlaySound( 0x55B );
if (tick >= 230)
{
NextTick = DateTime.Now + TimeSpan.FromSeconds(0.2);
}
else if (tick >= 180)
{
NextTick = DateTime.Now + TimeSpan.FromSeconds(0.3);
}
else if (tick >= 100)
{
NextTick = DateTime.Now + TimeSpan.FromSeconds(0.4);
}
else if (tick >= 50)
{
NextTick = DateTime.Now + TimeSpan.FromSeconds(0.5);
}
else
NextTick = DateTime.Now + TimeSpan.FromSeconds(0.6);
}
if ( DateTime.Now >= Expire )
{
Stop();
if ( m_Table.Contains( dest ) )
m_Table.Remove( dest );
}
}
}
}
}
using System;
using System.Collections;
using Server.Targeting;
using Server.Network;
using Server.Mobiles;
using Server.Spells;
using Server.Misc;
using Server.Items;
namespace Server.Spells.Eighth
{
public class FireballsSpell : MagerySpell
{
private static readonly SpellInfo m_Info = new SpellInfo(
"Fire balls", "Fire balls",
269,
9020,
false,
Reagent.Bloodmoss,
Reagent.MandrakeRoot,
Reagent.SpidersSilk);
public FireballsSpell(Mobile caster, Item scroll)
: base(caster, scroll, m_Info)
{
}
public override SpellCircle Circle
{
get
{
return SpellCircle.Eighth;
}
}
private static Hashtable m_Table = new Hashtable();
public override double RequiredSkill
{
get
{
return 120.0;
}
}
public static bool HasEffect(Mobile m)
{
return (m_Table[m] != null);
}
public static void RemoveEffect(Mobile m)
{
Timer t = (Timer)m_Table[m];
if (t != null)
{
t.Stop();
m_Table.Remove(m);
}
}
public override void OnCast()
{
Caster.Target = new InternalTarget(this);
}
public void Target(Mobile m)
{
if (!Caster.CanSee(m))
{
Caster.SendLocalizedMessage(500237);
}
else if (CheckBSequence(m, false))
{
SpellHelper.Turn(Caster, m);
double damage = Caster.Skills[SkillName.Magery].Value / 3.0;
Timer t = new InternalTimer(this, m, Caster);
t.Start();
m_Table[m] = t;
}
FinishSequence();
}
private class InternalTarget : Target
{
private FireballsSpell m_Owner;
public InternalTarget(FireballsSpell owner) : base(12, false, TargetFlags.Beneficial)
{
m_Owner = owner;
}
protected override void OnTarget(Mobile from, object o)
{
if (o is Mobile)
{
m_Owner.Target((Mobile)o);
}
}
protected override void OnTargetFinish(Mobile from)
{
m_Owner.FinishSequence();
}
}
private class InternalTimer : Timer
{
private Mobile dest, source;
private DateTime NextTick;
private DateTime Expire;
private Spell spell;
public InternalTimer(Spell spell, Mobile m, Mobile from) : base(TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(0.1))
{
dest = m;
source = from;
this.spell = spell;
Priority = TimerPriority.FiftyMS;
Expire = DateTime.UtcNow + TimeSpan.FromSeconds(1.5);
}
protected override void OnTick()
{
if (!dest.CheckAlive())
{
Stop();
m_Table.Remove(dest);
}
if (DateTime.UtcNow < NextTick)
return;
if (DateTime.UtcNow >= NextTick)
{
double tick = source.Skills[SkillName.Magery].Value;
double damage = tick / 3.0;
source.MovingParticles(dest, 0xA61B, 7, 0, false, true, 9502, 4019, 0x160);
dest.PlaySound(0x55B);
SpellHelper.Damage(spell, dest, damage, 0, 100, 0, 0, 0);
if (tick >= 230)
{
NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.2);
}
else if (tick >= 180)
{
NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.3);
}
else if (tick >= 100)
{
NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.4);
}
else if (tick >= 50)
{
NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.5);
}
else
NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.6);
}
if (DateTime.UtcNow >= Expire)
{
Stop();
if (m_Table.Contains(dest))
m_Table.Remove(dest);
}
}
}
}
}
Wow... Thank you so much from the bottom of my heart. In fact, I spent a lot of time on this, but I failed. Thank you so much. I'm very happy to solve it well. I hope you will always be full of blessings. Thank you so much.Line 157 is
NextTick = DateTime.Now + TimeSpan.FromSeconds(0.2);
And I do not see any SpellHelper in your code
----
And the error you had, from SpellHelper.Damage means that your source is wrong. It shouldnt be source, but the Spell you use.
Therefor you need to be able to access the Spell itself.
C#:using System; using System.Collections; using Server.Targeting; using Server.Network; using Server.Mobiles; using Server.Spells; using Server.Misc; using Server.Items; namespace Server.Spells.Eighth { public class FireballsSpell : MagerySpell { private static readonly SpellInfo m_Info = new SpellInfo( "Fire balls", "Fire balls", 269, 9020, false, Reagent.Bloodmoss, Reagent.MandrakeRoot, Reagent.SpidersSilk); public FireballsSpell(Mobile caster, Item scroll) : base(caster, scroll, m_Info) { } public override SpellCircle Circle { get { return SpellCircle.Eighth; } } private static Hashtable m_Table = new Hashtable(); public override double RequiredSkill { get { return 120.0; } } public static bool HasEffect(Mobile m) { return (m_Table[m] != null); } public static void RemoveEffect(Mobile m) { Timer t = (Timer)m_Table[m]; if (t != null) { t.Stop(); m_Table.Remove(m); } } public override void OnCast() { Caster.Target = new InternalTarget(this); } public void Target(Mobile m) { if (!Caster.CanSee(m)) { Caster.SendLocalizedMessage(500237); } else if (CheckBSequence(m, false)) { SpellHelper.Turn(Caster, m); double damage = Caster.Skills[SkillName.Magery].Value / 3.0; Timer t = new InternalTimer(this, m, Caster); t.Start(); m_Table[m] = t; } FinishSequence(); } private class InternalTarget : Target { private FireballsSpell m_Owner; public InternalTarget(FireballsSpell owner) : base(12, false, TargetFlags.Beneficial) { m_Owner = owner; } protected override void OnTarget(Mobile from, object o) { if (o is Mobile) { m_Owner.Target((Mobile)o); } } protected override void OnTargetFinish(Mobile from) { m_Owner.FinishSequence(); } } private class InternalTimer : Timer { private Mobile dest, source; private DateTime NextTick; private DateTime Expire; private Spell spell; public InternalTimer(Spell spell, Mobile m, Mobile from) : base(TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(0.1)) { dest = m; source = from; this.spell = spell; Priority = TimerPriority.FiftyMS; Expire = DateTime.UtcNow + TimeSpan.FromSeconds(1.5); } protected override void OnTick() { if (!dest.CheckAlive()) { Stop(); m_Table.Remove(dest); } if (DateTime.UtcNow < NextTick) return; if (DateTime.UtcNow >= NextTick) { double tick = source.Skills[SkillName.Magery].Value; double damage = tick / 3.0; source.MovingParticles(dest, 0xA61B, 7, 0, false, true, 9502, 4019, 0x160); dest.PlaySound(0x55B); SpellHelper.Damage(spell, dest, damage, 0, 100, 0, 0, 0); if (tick >= 230) { NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.2); } else if (tick >= 180) { NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.3); } else if (tick >= 100) { NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.4); } else if (tick >= 50) { NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.5); } else NextTick = DateTime.UtcNow + TimeSpan.FromSeconds(0.6); } if (DateTime.UtcNow >= Expire) { Stop(); if (m_Table.Contains(dest)) m_Table.Remove(dest); } } } } }
PyrO, I'm sorry to keep bothering you. I have one more question. I would like to ask you a question because the above fireballs magic is not affected by "Attributes.SpellDamage". Can you tell me what I should add to be affected by "Attributes.SpellDamage"?You are welcome and good luck with the project!
Oh! I found a solution. Thank you from the bottom of my heart for your response!You had damage defined as
double damage = tick / 3.0;
so in that case you would want to multiply that by the spelldamage increase of the player
We use essential cookies to make this site work, and optional cookies to enhance your experience.