fix: Codegens spell items (#1765)

### Summary
- Fixes some serialization issues with spell items
- Fixes accessibility issues with field spells and removes the awkward "InternalItem" concept
- Codegens the fields/items used in spells
This commit is contained in:
Kamron Batman 2024-05-08 19:48:39 -07:00 committed by GitHub
parent 542b672d9d
commit 8efa90b3ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1389 additions and 1507 deletions

View file

@ -147,7 +147,7 @@ public partial class Firebomb : Item
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
new FireFieldSpell.FireFieldItem(itemID, targetLoc, m_LitBy, Map, TimeSpan.FromSeconds(9), i);
new FireFieldItem(itemID, targetLoc, m_LitBy, Map, TimeSpan.FromSeconds(9), i);
}
}

View file

@ -0,0 +1,14 @@
{
"version": 0,
"type": "Server.Items.ArcaneFocus",
"properties": [
{
"name": "StrengthBonus",
"type": "int",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
}
]
}

View file

@ -0,0 +1,14 @@
{
"version": 1,
"type": "Server.Items.TransientItem",
"properties": [
{
"name": "Expiration",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"DeltaTime"
]
}
]
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Mobiles.ArcaneFey"
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Mobiles.ArcaneFiend"
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Mobiles.NatureFury"
}

View file

@ -0,0 +1,19 @@
{
"version": 0,
"type": "Server.Spells.Fifth.PoisonField",
"properties": [
{
"name": "Caster",
"type": "Server.Mobile",
"rule": "SerializableInterfaceMigrationRule"
},
{
"name": "End",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"DeltaTime"
]
}
]
}

View file

@ -0,0 +1,27 @@
{
"version": 0,
"type": "Server.Spells.Fourth.FireFieldItem",
"properties": [
{
"name": "Damage",
"type": "int",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "Caster",
"type": "Server.Mobile",
"rule": "SerializableInterfaceMigrationRule"
},
{
"name": "End",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"DeltaTime"
]
}
]
}

View file

@ -0,0 +1,19 @@
{
"version": 1,
"type": "Server.Spells.Seventh.EnergyField",
"properties": [
{
"name": "Caster",
"type": "Server.Mobile",
"rule": "SerializableInterfaceMigrationRule"
},
{
"name": "End",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"DeltaTime"
]
}
]
}

View file

@ -0,0 +1,4 @@
{
"version": 0,
"type": "Server.Spells.Seventh.GateTravelMoongate"
}

View file

@ -0,0 +1,19 @@
{
"version": 0,
"type": "Server.Spells.Sixth.ParalyzeField",
"properties": [
{
"name": "Caster",
"type": "Server.Mobile",
"rule": "SerializableInterfaceMigrationRule"
},
{
"name": "End",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"DeltaTime"
]
}
]
}

View file

@ -0,0 +1,19 @@
{
"version": 0,
"type": "Server.Spells.Third.WallOfStone",
"properties": [
{
"name": "Caster",
"type": "Server.Mobile",
"rule": "SerializableInterfaceMigrationRule"
},
{
"name": "End",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"DeltaTime"
]
}
]
}

View file

@ -1,275 +1,248 @@
using System;
using ModernUO.Serialization;
using Server.Collections;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
namespace Server.Spells.Fifth
namespace Server.Spells.Fifth;
public class PoisonFieldSpell : MagerySpell, ISpellTargetingPoint3D
{
public class PoisonFieldSpell : MagerySpell, ISpellTargetingPoint3D
private static readonly SpellInfo _info = new(
"Poison Field",
"In Nox Grav",
230,
9052,
false,
Reagent.BlackPearl,
Reagent.Nightshade,
Reagent.SpidersSilk
);
public PoisonFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
private static readonly SpellInfo _info = new(
"Poison Field",
"In Nox Grav",
230,
9052,
false,
Reagent.BlackPearl,
Reagent.Nightshade,
Reagent.SpidersSilk
);
}
public PoisonFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
public override SpellCircle Circle => SpellCircle.Fifth;
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20B);
var itemID = eastToWest ? 0x3915 : 0x3922;
var duration = Core.Expansion switch
{
Expansion.None => TimeSpan.FromSeconds(20),
< Expansion.LBR => TimeSpan.FromSeconds(15 + Caster.Skills.Magery.Value * 0.4),
_ => TimeSpan.FromSeconds(3 + Caster.Skills.Magery.Value * 0.4)
};
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
new PoisonField(itemID, targetLoc, Caster, Caster.Map, duration, i);
}
}
public override SpellCircle Circle => SpellCircle.Fifth;
FinishSequence();
}
public void Target(IPoint3D p)
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
}
[DispellableField]
[SerializationGenerator(0, false)]
public partial class PoisonField : Item
{
[SerializableField(0)]
private Mobile _caster;
[DeltaDateTime]
[SerializableField(1)]
private DateTime _end;
private Timer _timer;
public PoisonField(int itemID, Point3D loc, Mobile caster, Map map, TimeSpan duration, int val) : base(itemID)
{
var canFit = SpellHelper.AdjustField(ref loc, map, 12, false);
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
_caster = caster;
_end = Core.Now + duration;
_timer = new InternalTimer(this, TimeSpan.FromSeconds(val.Abs() * 0.2), caster.InLOS(this), canFit);
_timer.Start();
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
_timer?.Stop();
}
[AfterDeserialization]
private void AfterDeserialization()
{
_timer = new InternalTimer(this, TimeSpan.Zero, true, true);
_timer.Start();
}
public void ApplyPoisonTo(Mobile m)
{
if (_caster == null)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20B);
var itemID = eastToWest ? 0x3915 : 0x3922;
var duration = Core.Expansion switch
{
Expansion.None => TimeSpan.FromSeconds(20),
< Expansion.LBR => TimeSpan.FromSeconds(15 + Caster.Skills.Magery.Value * 0.4),
_ => TimeSpan.FromSeconds(3 + Caster.Skills.Magery.Value * 0.4)
};
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
new InternalItem(itemID, targetLoc, Caster, Caster.Map, duration, i);
}
}
FinishSequence();
return;
}
public override void OnCast()
int level;
if (Core.AOS)
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
level = (_caster.Skills.Magery.Value + _caster.Skills.Poisoning.Value) switch
{
> 199.8 => 3,
> 170.2 => 2,
> 130.2 => 1,
_ => 0
};
}
else
{
level = 1;
}
[DispellableField]
public class InternalItem : Item
if (m.ApplyPoison(_caster, Poison.GetPoison(level)) is ApplyPoisonResult.Poisoned or ApplyPoisonResult.HigherPoisonActive)
{
private Mobile m_Caster;
private DateTime m_End;
private Timer m_Timer;
public InternalItem(int itemID, Point3D loc, Mobile caster, Map map, TimeSpan duration, int val) : base(itemID)
if (SpellHelper.CanRevealCaster(m))
{
var canFit = SpellHelper.AdjustField(ref loc, map, 12, false);
_caster.RevealingAction();
}
}
Visible = false;
Movable = false;
Light = LightType.Circle300;
(m as BaseCreature)?.OnHarmfulSpell(_caster);
}
MoveToWorld(loc, map);
public override bool OnMoveOver(Mobile m)
{
if (Visible && _caster != null && (!Core.AOS || m != _caster) &&
SpellHelper.ValidIndirectTarget(_caster, m) && _caster.CanBeHarmful(m, false))
{
_caster.DoHarmful(m);
m_Caster = caster;
ApplyPoisonTo(m);
m.PlaySound(0x474);
}
m_End = Core.Now + duration;
return true;
}
m_Timer = new InternalTimer(this, TimeSpan.FromSeconds(val.Abs() * 0.2), caster.InLOS(this), canFit);
m_Timer.Start();
private class InternalTimer : Timer
{
private readonly bool m_CanFit;
private readonly bool m_InLOS;
private readonly PoisonField m_Item;
public InternalTimer(PoisonField item, TimeSpan delay, bool inLOS, bool canFit) : base(
delay,
TimeSpan.FromSeconds(1.5)
)
{
m_Item = item;
m_InLOS = inLOS;
m_CanFit = canFit;
}
protected override void OnTick()
{
if (m_Item.Deleted)
{
return;
}
public InternalItem(Serial serial) : base(serial)
if (!m_Item.Visible)
{
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Timer?.Stop();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
writer.Write(m_Caster);
writer.WriteDeltaTime(m_End);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
switch (version)
if (m_InLOS && m_CanFit)
{
case 1:
{
m_Caster = reader.ReadEntity<Mobile>();
goto case 0;
}
case 0:
{
m_End = reader.ReadDeltaTime();
m_Timer = new InternalTimer(this, TimeSpan.Zero, true, true);
m_Timer.Start();
break;
}
}
}
public void ApplyPoisonTo(Mobile m)
{
if (m_Caster == null)
{
return;
}
int level;
if (Core.AOS)
{
level = (m_Caster.Skills.Magery.Value + m_Caster.Skills.Poisoning.Value) switch
{
> 199.8 => 3,
> 170.2 => 2,
> 130.2 => 1,
_ => 0
};
m_Item.Visible = true;
}
else
{
level = 1;
m_Item.Delete();
}
if (m.ApplyPoison(m_Caster, Poison.GetPoison(level)) is ApplyPoisonResult.Poisoned or ApplyPoisonResult.HigherPoisonActive)
if (!m_Item.Deleted)
{
if (SpellHelper.CanRevealCaster(m))
{
m_Caster.RevealingAction();
}
m_Item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(m_Item.Location, m_Item.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5040
);
}
(m as BaseCreature)?.OnHarmfulSpell(m_Caster);
}
public override bool OnMoveOver(Mobile m)
else if (Core.Now > m_Item._end)
{
if (Visible && m_Caster != null && (!Core.AOS || m != m_Caster) &&
SpellHelper.ValidIndirectTarget(m_Caster, m) && m_Caster.CanBeHarmful(m, false))
{
m_Caster.DoHarmful(m);
ApplyPoisonTo(m);
m.PlaySound(0x474);
}
return true;
m_Item.Delete();
Stop();
}
private class InternalTimer : Timer
else
{
private readonly bool m_CanFit;
private readonly bool m_InLOS;
private readonly InternalItem m_Item;
var map = m_Item.Map;
var caster = m_Item._caster;
public InternalTimer(InternalItem item, TimeSpan delay, bool inLOS, bool canFit) : base(
delay,
TimeSpan.FromSeconds(1.5)
)
if (map != null && caster != null)
{
m_Item = item;
m_InLOS = inLOS;
m_CanFit = canFit;
}
var eastToWest = m_Item.ItemID == 0x3915;
var bounds = new Rectangle2D(
m_Item.X - (eastToWest ? 0 : 1),
m_Item.Y - (eastToWest ? 1 : 0),
eastToWest ? 1 : 2,
eastToWest ? 2 : 1
);
protected override void OnTick()
{
if (m_Item.Deleted)
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in map.GetMobilesInBounds(bounds))
{
return;
}
if (!m_Item.Visible)
{
if (m_InLOS && m_CanFit)
if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
{
m_Item.Visible = true;
}
else
{
m_Item.Delete();
}
if (!m_Item.Deleted)
{
m_Item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(m_Item.Location, m_Item.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5040
);
queue.Enqueue(m);
}
}
else if (Core.Now > m_Item.m_End)
while (queue.Count > 0)
{
m_Item.Delete();
Stop();
}
else
{
var map = m_Item.Map;
var caster = m_Item.m_Caster;
var m = queue.Dequeue();
if (map != null && caster != null)
{
var eastToWest = m_Item.ItemID == 0x3915;
var bounds = new Rectangle2D(
m_Item.X - (eastToWest ? 0 : 1),
m_Item.Y - (eastToWest ? 1 : 0),
eastToWest ? 1 : 2,
eastToWest ? 2 : 1
);
caster.DoHarmful(m);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in map.GetMobilesInBounds(bounds))
{
if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
{
queue.Enqueue(m);
}
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
caster.DoHarmful(m);
m_Item.ApplyPoisonTo(m);
m.PlaySound(0x474);
}
}
m_Item.ApplyPoisonTo(m);
m.PlaySound(0x474);
}
}
}

View file

@ -1,173 +1,235 @@
using System;
using ModernUO.Serialization;
using Server.Collections;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
namespace Server.Spells.Fourth
namespace Server.Spells.Fourth;
public class FireFieldSpell : MagerySpell, ISpellTargetingPoint3D
{
public class FireFieldSpell : MagerySpell, ISpellTargetingPoint3D
private static readonly SpellInfo _info = new(
"Fire Field",
"In Flam Grav",
215,
9041,
false,
Reagent.BlackPearl,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
public FireFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
private static readonly SpellInfo _info = new(
"Fire Field",
"In Flam Grav",
215,
9041,
false,
Reagent.BlackPearl,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
}
public FireFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
public override SpellCircle Circle => SpellCircle.Fourth;
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20C);
var itemID = eastToWest ? 0x398C : 0x3996;
var duration = Core.Expansion switch
{
Expansion.None => TimeSpan.FromSeconds(20),
< Expansion.LBR => TimeSpan.FromSeconds(Caster.Skills.Magery.Value),
_ => TimeSpan.FromSeconds(4.0 + Caster.Skills.Magery.Value * 0.5)
};
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
new FireFieldItem(itemID, targetLoc, Caster, Caster.Map, duration, i);
}
}
public override SpellCircle Circle => SpellCircle.Fourth;
FinishSequence();
}
public void Target(IPoint3D p)
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
}
[DispellableField]
[SerializationGenerator(0, false)]
public partial class FireFieldItem : Item
{
[SerializableField(0)]
private int _damage;
[SerializableField(1)]
private Mobile _caster;
[DeltaDateTime]
[SerializableField(2)]
private DateTime _end;
private Timer _timer;
public FireFieldItem(
int itemID, Point3D loc, Mobile caster, Map map, TimeSpan duration, int val,
int damage = 2
) : base(itemID)
{
var canFit = SpellHelper.AdjustField(ref loc, map, 12, false);
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
_caster = caster;
_damage = damage;
_end = Core.Now + duration;
_timer = new InternalTimer(this, TimeSpan.FromSeconds(val.Abs() * 0.2), caster.InLOS(this), canFit);
_timer.Start();
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
_timer?.Stop();
}
[AfterDeserialization]
private void AfterDeserialization()
{
_timer = new InternalTimer(this, TimeSpan.Zero, true, true);
_timer.Start();
}
public override bool OnMoveOver(Mobile m)
{
if (Visible && _caster != null && (!Core.AOS || m != _caster) &&
SpellHelper.ValidIndirectTarget(_caster, m) && _caster.CanBeHarmful(m, false))
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
if (SpellHelper.CanRevealCaster(m))
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20C);
var itemID = eastToWest ? 0x398C : 0x3996;
var duration = Core.Expansion switch
{
Expansion.None => TimeSpan.FromSeconds(20),
< Expansion.LBR => TimeSpan.FromSeconds(Caster.Skills.Magery.Value),
_ => TimeSpan.FromSeconds(4.0 + Caster.Skills.Magery.Value * 0.5)
};
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
new FireFieldItem(itemID, targetLoc, Caster, Caster.Map, duration, i);
}
_caster.RevealingAction();
}
FinishSequence();
_caster.DoHarmful(m);
var damage = _damage;
if (!Core.AOS && m.CheckSkill(SkillName.MagicResist, 0.0, 30.0))
{
damage = 1;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
AOS.Damage(m, _caster, damage, 0, 100, 0, 0, 0);
m.PlaySound(0x208);
(m as BaseCreature)?.OnHarmfulSpell(_caster);
}
public override void OnCast()
return true;
}
private class InternalTimer : Timer
{
private readonly bool _canFit;
private readonly bool _inLOS;
private readonly FireFieldItem _item;
public InternalTimer(FireFieldItem item, TimeSpan delay, bool inLOS, bool canFit) : base(delay, TimeSpan.FromSeconds(1.0))
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
_item = item;
_inLOS = inLOS;
_canFit = canFit;
}
[DispellableField]
public class FireFieldItem : Item
protected override void OnTick()
{
private Mobile m_Caster;
private int m_Damage;
private DateTime m_End;
private Timer m_Timer;
public FireFieldItem(
int itemID, Point3D loc, Mobile caster, Map map, TimeSpan duration, int val,
int damage = 2
) : base(itemID)
if (_item.Deleted)
{
var canFit = SpellHelper.AdjustField(ref loc, map, 12, false);
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
m_Caster = caster;
m_Damage = damage;
m_End = Core.Now + duration;
m_Timer = new InternalTimer(this, TimeSpan.FromSeconds(val.Abs() * 0.2), caster.InLOS(this), canFit);
m_Timer.Start();
return;
}
public FireFieldItem(Serial serial) : base(serial)
if (!_item.Visible)
{
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Timer?.Stop();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(2); // version
writer.Write(m_Damage);
writer.Write(m_Caster);
writer.WriteDeltaTime(m_End);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
switch (version)
if (_inLOS && _canFit)
{
case 2:
{
m_Damage = reader.ReadInt();
goto case 1;
}
case 1:
{
m_Caster = reader.ReadEntity<Mobile>();
goto case 0;
}
case 0:
{
m_End = reader.ReadDeltaTime();
m_Timer = new InternalTimer(this, TimeSpan.Zero, true, true);
m_Timer.Start();
break;
}
_item.Visible = true;
}
else
{
_item.Delete();
}
if (version < 2)
if (!_item.Deleted)
{
m_Damage = 2;
_item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(_item.Location, _item.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5029
);
}
}
public override bool OnMoveOver(Mobile m)
else if (Core.Now > _item._end)
{
if (Visible && m_Caster != null && (!Core.AOS || m != m_Caster) &&
SpellHelper.ValidIndirectTarget(m_Caster, m) && m_Caster.CanBeHarmful(m, false))
_item.Delete();
Stop();
}
else
{
var map = _item.Map;
var caster = _item._caster;
if (map == null || caster == null)
{
return;
}
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in _item.GetMobilesAt())
{
if (m.Z + 16 > _item.Z && _item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
{
queue.Enqueue(m);
}
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
if (SpellHelper.CanRevealCaster(m))
{
m_Caster.RevealingAction();
caster.RevealingAction();
}
m_Caster.DoHarmful(m);
caster.DoHarmful(m);
var damage = m_Damage;
var damage = _item._damage;
if (!Core.AOS && m.CheckSkill(SkillName.MagicResist, 0.0, 30.0))
{
@ -176,109 +238,10 @@ namespace Server.Spells.Fourth
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
AOS.Damage(m, m_Caster, damage, 0, 100, 0, 0, 0);
AOS.Damage(m, caster, damage, 0, 100, 0, 0, 0);
m.PlaySound(0x208);
(m as BaseCreature)?.OnHarmfulSpell(m_Caster);
}
return true;
}
private class InternalTimer : Timer
{
private readonly bool m_CanFit;
private readonly bool m_InLOS;
private readonly FireFieldItem m_Item;
public InternalTimer(FireFieldItem item, TimeSpan delay, bool inLOS, bool canFit) : base(delay, TimeSpan.FromSeconds(1.0))
{
m_Item = item;
m_InLOS = inLOS;
m_CanFit = canFit;
}
protected override void OnTick()
{
if (m_Item.Deleted)
{
return;
}
if (!m_Item.Visible)
{
if (m_InLOS && m_CanFit)
{
m_Item.Visible = true;
}
else
{
m_Item.Delete();
}
if (!m_Item.Deleted)
{
m_Item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(m_Item.Location, m_Item.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5029
);
}
}
else if (Core.Now > m_Item.m_End)
{
m_Item.Delete();
Stop();
}
else
{
var map = m_Item.Map;
var caster = m_Item.m_Caster;
if (map == null || caster == null)
{
return;
}
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in m_Item.GetMobilesAt())
{
if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
{
queue.Enqueue(m);
}
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
if (SpellHelper.CanRevealCaster(m))
{
caster.RevealingAction();
}
caster.DoHarmful(m);
var damage = m_Item.m_Damage;
if (!Core.AOS && m.CheckSkill(SkillName.MagicResist, 0.0, 30.0))
{
damage = 1;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
AOS.Damage(m, caster, damage, 0, 100, 0, 0, 0);
m.PlaySound(0x208);
(m as BaseCreature)?.OnHarmfulSpell(caster);
}
}
(m as BaseCreature)?.OnHarmfulSpell(caster);
}
}
}

View file

@ -1,168 +1,146 @@
using System;
using ModernUO.Serialization;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
namespace Server.Spells.Seventh
namespace Server.Spells.Seventh;
public class EnergyFieldSpell : MagerySpell, ISpellTargetingPoint3D
{
public class EnergyFieldSpell : MagerySpell, ISpellTargetingPoint3D
private static readonly SpellInfo _info = new(
"Energy Field",
"In Sanct Grav",
221,
9022,
false,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
public EnergyFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
private static readonly SpellInfo _info = new(
"Energy Field",
"In Sanct Grav",
221,
9022,
false,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.SpidersSilk,
Reagent.SulfurousAsh
);
}
public EnergyFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
public override SpellCircle Circle => SpellCircle.Seventh;
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20B);
TimeSpan duration = Core.AOS
? TimeSpan.FromSeconds((15 + Caster.Skills.Magery.Value * 2) / 7.0)
: TimeSpan.FromSeconds(Caster.Skills.Magery.Value * 0.28 + 2.0);
var itemID = eastToWest ? 0x3946 : 0x3956;
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
var canFit = SpellHelper.AdjustField(ref targetLoc, Caster.Map, 12, false);
if (!canFit)
{
continue;
}
Item item = new EnergyField(targetLoc, Caster.Map, duration, itemID, Caster);
item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(targetLoc, Caster.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5051
);
}
}
public override SpellCircle Circle => SpellCircle.Seventh;
FinishSequence();
}
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20B);
TimeSpan duration = Core.AOS
? TimeSpan.FromSeconds((15 + Caster.Skills.Magery.Value * 2) / 7.0)
: TimeSpan.FromSeconds(Caster.Skills.Magery.Value * 0.28 + 2.0);
var itemID = eastToWest ? 0x3946 : 0x3956;
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
var canFit = SpellHelper.AdjustField(ref targetLoc, Caster.Map, 12, false);
if (!canFit)
{
continue;
}
Item item = new InternalItem(targetLoc, Caster.Map, duration, itemID, Caster);
item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(targetLoc, Caster.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5051
);
}
}
FinishSequence();
}
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
[DispellableField]
private class InternalItem : Item
{
private readonly Mobile m_Caster;
private readonly Timer m_Timer;
public InternalItem(Point3D loc, Map map, TimeSpan duration, int itemID, Mobile caster) : base(itemID)
{
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
m_Caster = caster;
if (caster.InLOS(this))
{
Visible = true;
}
else
{
Delete();
}
if (Deleted)
{
return;
}
m_Timer = new InternalTimer(this, duration);
m_Timer.Start();
}
public InternalItem(Serial serial) : base(serial)
{
m_Timer = new InternalTimer(this, TimeSpan.FromSeconds(5.0));
m_Timer.Start();
}
public override bool BlocksFit => true;
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
}
public override bool OnMoveOver(Mobile m)
{
if (m is not PlayerMobile)
{
return base.OnMoveOver(m);
}
var noto = Notoriety.Compute(m_Caster, m);
return noto != Notoriety.Enemy && noto != Notoriety.Ally && base.OnMoveOver(m);
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Timer?.Stop();
}
private class InternalTimer : Timer
{
private readonly InternalItem m_Item;
public InternalTimer(InternalItem item, TimeSpan duration) : base(duration)
{
m_Item = item;
}
protected override void OnTick()
{
m_Item.Delete();
}
}
}
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
}
[DispellableField]
[SerializationGenerator(1, false)]
public partial class EnergyField : Item
{
[SerializableField(0)]
private Mobile _caster;
[DeltaDateTime]
[SerializableField(1)]
private DateTime _end;
private TimerExecutionToken _timer;
public EnergyField(Point3D loc, Map map, TimeSpan duration, int itemID, Mobile caster) : base(itemID)
{
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
_caster = caster;
if (caster.InLOS(this))
{
Visible = true;
}
else
{
Delete();
return;
}
Timer.StartTimer(duration, Delete, out _timer);
}
public override bool BlocksFit => true;
private void Deserialize(IGenericReader reader, int version)
{
// Do nothing, no caster
}
[AfterDeserialization]
private void AfterDeserialization()
{
var duration = _end > DateTime.MinValue ? _end - Core.Now : TimeSpan.FromSeconds(5);
Timer.StartTimer(duration, Delete, out _timer);
}
public override bool OnMoveOver(Mobile m)
{
if (m is not PlayerMobile)
{
return base.OnMoveOver(m);
}
var noto = Notoriety.Compute(_caster, m);
return noto != Notoriety.Enemy && noto != Notoriety.Ally && base.OnMoveOver(m);
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
_timer.Cancel();
}
}

View file

@ -5,223 +5,206 @@ using Server.Items;
using Server.Misc;
using Server.Mobiles;
namespace Server.Spells.Seventh
namespace Server.Spells.Seventh;
public class GateTravelSpell : MagerySpell, IRecallSpell
{
public partial class GateTravelSpell : MagerySpell, IRecallSpell
private static readonly SpellInfo _info = new(
"Gate Travel",
"Vas Rel Por",
263,
9032,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.SulfurousAsh
);
private readonly RunebookEntry m_Entry;
public GateTravelSpell(Mobile caster, Item scroll) : base(caster, scroll, _info)
{
private static readonly SpellInfo _info = new(
"Gate Travel",
"Vas Rel Por",
263,
9032,
Reagent.BlackPearl,
Reagent.MandrakeRoot,
Reagent.SulfurousAsh
);
}
private readonly RunebookEntry m_Entry;
public GateTravelSpell(Mobile caster, RunebookEntry entry = null, Item scroll = null) :
base(caster, scroll, _info) => m_Entry = entry;
public GateTravelSpell(Mobile caster, Item scroll) : base(caster, scroll, _info)
public override SpellCircle Circle => SpellCircle.Seventh;
public void Effect(Point3D loc, Map map, bool checkMulti)
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (map == null || !Core.AOS && Caster.Map != map)
{
Caster.SendLocalizedMessage(1005570); // You can not gate to another facet.
}
else if (!SpellHelper.CheckTravel(Caster, TravelCheckType.GateFrom, out var failureMessage))
{
failureMessage.SendMessageTo(Caster);
}
else if (!SpellHelper.CheckTravel(Caster, map, loc, TravelCheckType.GateTo, out failureMessage))
{
failureMessage.SendMessageTo(Caster);
}
else if (map == Map.Felucca && Caster is PlayerMobile mobile && mobile.Young)
{
mobile.SendLocalizedMessage(1049543); // You decide against traveling to Felucca while you are still young.
}
else if (Caster.Kills >= 5 && map != Map.Felucca)
{
Caster.SendLocalizedMessage(1019004); // You are not allowed to travel there.
}
else if (Caster.Criminal)
{
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
}
else if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
}
else if (!map.CanSpawnMobile(loc.X, loc.Y, loc.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (checkMulti && SpellHelper.CheckMulti(loc, map))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
// SE restricted stacking gates
else if (Core.SE && (GateExistsAt(map, loc) || GateExistsAt(Caster.Map, Caster.Location)))
{
Caster.SendLocalizedMessage(1071242); // There is already a gate there.
}
else if (CheckSequence())
{
Caster.SendLocalizedMessage(501024); // You open a magical gate to another location
Effects.PlaySound(Caster.Location, Caster.Map, 0x20E);
var firstGate = new GateTravelMoongate(loc, map);
firstGate.MoveToWorld(Caster.Location, Caster.Map);
Effects.PlaySound(loc, map, 0x20E);
var secondGate = new GateTravelMoongate(Caster.Location, Caster.Map);
secondGate.MoveToWorld(loc, map);
firstGate.LinkedGate = secondGate;
secondGate.LinkedGate = firstGate;
}
public GateTravelSpell(Mobile caster, RunebookEntry entry = null, Item scroll = null) :
base(caster, scroll, _info) => m_Entry = entry;
FinishSequence();
}
public override SpellCircle Circle => SpellCircle.Seventh;
public void Effect(Point3D loc, Map map, bool checkMulti)
public override void OnCast()
{
if (m_Entry == null)
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (map == null || !Core.AOS && Caster.Map != map)
{
Caster.SendLocalizedMessage(1005570); // You can not gate to another facet.
}
else if (!SpellHelper.CheckTravel(Caster, TravelCheckType.GateFrom, out var failureMessage))
{
failureMessage.SendMessageTo(Caster);
}
else if (!SpellHelper.CheckTravel(Caster, map, loc, TravelCheckType.GateTo, out failureMessage))
{
failureMessage.SendMessageTo(Caster);
}
else if (map == Map.Felucca && Caster is PlayerMobile mobile && mobile.Young)
{
mobile.SendLocalizedMessage(1049543); // You decide against traveling to Felucca while you are still young.
}
else if (Caster.Kills >= 5 && map != Map.Felucca)
{
Caster.SendLocalizedMessage(1019004); // You are not allowed to travel there.
}
else if (Caster.Criminal)
{
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
}
else if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
}
else if (!map.CanSpawnMobile(loc.X, loc.Y, loc.Z))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
else if (checkMulti && SpellHelper.CheckMulti(loc, map))
{
Caster.SendLocalizedMessage(501942); // That location is blocked.
}
// SE restricted stacking gates
else if (Core.SE && (GateExistsAt(map, loc) || GateExistsAt(Caster.Map, Caster.Location)))
{
Caster.SendLocalizedMessage(1071242); // There is already a gate there.
}
else if (CheckSequence())
{
Caster.SendLocalizedMessage(501024); // You open a magical gate to another location
Effects.PlaySound(Caster.Location, Caster.Map, 0x20E);
var firstGate = new InternalItem(loc, map);
firstGate.MoveToWorld(Caster.Location, Caster.Map);
Effects.PlaySound(loc, map, 0x20E);
var secondGate = new InternalItem(Caster.Location, Caster.Map);
secondGate.MoveToWorld(loc, map);
firstGate.LinkedGate = secondGate;
secondGate.LinkedGate = firstGate;
}
FinishSequence();
Caster.Target = new RecallSpellTarget(this, false);
}
public override void OnCast()
else
{
if (m_Entry == null)
{
Caster.Target = new RecallSpellTarget(this, false);
}
else
{
Effect(m_Entry.Location, m_Entry.Map, true);
}
Effect(m_Entry.Location, m_Entry.Map, true);
}
}
public override bool CheckCast()
public override bool CheckCast()
{
if (Sigil.ExistsOn(Caster))
{
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
return false;
}
if (Caster.Criminal)
{
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
return false;
}
if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
return false;
}
if (!SpellHelper.CheckTravel(Caster, TravelCheckType.GateFrom, out var failureMessage))
{
failureMessage.SendMessageTo(Caster);
return false;
}
return true;
}
private static bool GateExistsAt(Map map, Point3D loc)
{
foreach (var item in map.GetItemsAt(loc))
{
if (item is Moongate or PublicMoongate)
{
return true;
}
}
Caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
return false;
}
[DispellableField]
[SerializationGenerator(0)]
private partial class InternalItem : Moongate
if (Caster.Criminal)
{
public InternalItem(Point3D target, Map map) : base(target, map)
Caster.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
return false;
}
if (SpellHelper.CheckCombat(Caster))
{
Caster.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
return false;
}
if (!SpellHelper.CheckTravel(Caster, TravelCheckType.GateFrom, out var failureMessage))
{
failureMessage.SendMessageTo(Caster);
return false;
}
return true;
}
private static bool GateExistsAt(Map map, Point3D loc)
{
foreach (var item in map.GetItemsAt(loc))
{
if (item is Moongate or PublicMoongate)
{
Map = map;
if (ShowFeluccaWarning && map == Map.Felucca)
{
ItemID = 0xDDA;
}
Dispellable = true;
var t = new InternalTimer(this);
t.Start();
}
[CommandProperty(AccessLevel.GameMaster)]
public Moongate LinkedGate { get; set; }
public override bool ShowFeluccaWarning => Core.AOS;
public override void UseGate(Mobile m)
{
if (LinkedGate?.Deleted != false)
{
m.SendMessage("The other gate no longer exists.");
return;
}
var target = LinkedGate.Location;
var targetMap = LinkedGate.TargetMap;
// TODO: Add boat permissions
// BaseBoat boat = BaseBoat.FindBoatAt(target, targetMap);
//
// if (boat != null && !boat.HasAccess(m))
// {
// m.SendLocalizedMessage(1116617); // You do not have permission to board this ship.
// return;
// }
Target = target;
TargetMap = targetMap;
base.UseGate(m);
}
[AfterDeserialization(false)]
private void AfterDeserialization()
{
Delete();
}
private class InternalTimer : Timer
{
private readonly Item m_Item;
public InternalTimer(Item item) : base(TimeSpan.FromSeconds(Core.T2A ? 30.0 : 10.0))
{
m_Item = item;
}
protected override void OnTick()
{
m_Item.Delete();
}
return true;
}
}
return false;
}
}
[DispellableField]
[SerializationGenerator(0)]
public partial class GateTravelMoongate : Moongate
{
public GateTravelMoongate(Point3D target, Map map) : base(target, map)
{
Map = map;
if (ShowFeluccaWarning && map == Map.Felucca)
{
ItemID = 0xDDA;
}
Dispellable = true;
Timer.StartTimer(TimeSpan.FromSeconds(Core.T2A ? 30.0 : 10.0), Delete);
}
[CommandProperty(AccessLevel.GameMaster)]
public Moongate LinkedGate { get; set; }
public override bool ShowFeluccaWarning => Core.AOS;
public override void UseGate(Mobile m)
{
if (LinkedGate?.Deleted != false)
{
m.SendMessage("The other gate no longer exists.");
return;
}
var target = LinkedGate.Location;
var targetMap = LinkedGate.TargetMap;
// TODO: Add boat permissions
// BaseBoat boat = BaseBoat.FindBoatAt(target, targetMap);
//
// if (boat != null && !boat.HasAccess(m))
// {
// m.SendLocalizedMessage(1116617); // You do not have permission to board this ship.
// return;
// }
Target = target;
TargetMap = targetMap;
base.UseGate(m);
}
[AfterDeserialization(false)]
private void AfterDeserialization()
{
Delete();
}
}

View file

@ -1,217 +1,177 @@
using System;
using ModernUO.Serialization;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
namespace Server.Spells.Sixth
namespace Server.Spells.Sixth;
public class ParalyzeFieldSpell : MagerySpell, ISpellTargetingPoint3D
{
public class ParalyzeFieldSpell : MagerySpell, ISpellTargetingPoint3D
private static readonly SpellInfo _info = new(
"Paralyze Field",
"In Ex Grav",
230,
9012,
false,
Reagent.BlackPearl,
Reagent.Ginseng,
Reagent.SpidersSilk
);
public ParalyzeFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
private static readonly SpellInfo _info = new(
"Paralyze Field",
"In Ex Grav",
230,
9012,
false,
Reagent.BlackPearl,
Reagent.Ginseng,
Reagent.SpidersSilk
);
}
public ParalyzeFieldSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
public override SpellCircle Circle => SpellCircle.Sixth;
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20B);
var itemID = eastToWest ? 0x3967 : 0x3979;
var duration = Core.Expansion switch
{
Expansion.None => TimeSpan.FromSeconds(20),
< Expansion.LBR => TimeSpan.FromSeconds(15.0 + Caster.Skills.Magery.Value / 3.0),
_ => TimeSpan.FromSeconds(3.0 + Caster.Skills.Magery.Value / 3.0)
};
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
if (!SpellHelper.AdjustField(ref targetLoc, Caster.Map, 12, false))
{
continue;
}
Item item = new ParalyzeField(Caster, itemID, targetLoc, Caster.Map, duration);
item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(targetLoc, Caster.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5048
);
}
}
public override SpellCircle Circle => SpellCircle.Sixth;
FinishSequence();
}
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x20B);
var itemID = eastToWest ? 0x3967 : 0x3979;
var duration = Core.Expansion switch
{
Expansion.None => TimeSpan.FromSeconds(20),
< Expansion.LBR => TimeSpan.FromSeconds(15.0 + Caster.Skills.Magery.Value / 3.0),
_ => TimeSpan.FromSeconds(3.0 + Caster.Skills.Magery.Value / 3.0)
};
for (var i = -2; i <= 2; ++i)
{
var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z);
if (!SpellHelper.AdjustField(ref targetLoc, Caster.Map, 12, false))
{
continue;
}
Item item = new InternalItem(Caster, itemID, targetLoc, Caster.Map, duration);
item.ProcessDelta();
Effects.SendLocationParticles(
EffectItem.Create(targetLoc, Caster.Map, EffectItem.DefaultDuration),
0x376A,
9,
10,
5048
);
}
}
FinishSequence();
}
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
[DispellableField]
public class InternalItem : Item
{
private Mobile m_Caster;
private DateTime m_End;
private Timer m_Timer;
public InternalItem(Mobile caster, int itemID, Point3D loc, Map map, TimeSpan duration) : base(itemID)
{
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
if (caster.InLOS(this))
{
Visible = true;
}
else
{
Delete();
}
if (Deleted)
{
return;
}
m_Caster = caster;
m_Timer = new InternalTimer(this, duration);
m_Timer.Start();
m_End = Core.Now + duration;
}
public InternalItem(Serial serial) : base(serial)
{
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Timer?.Stop();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write(m_Caster);
writer.WriteDeltaTime(m_End);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
switch (version)
{
case 0:
{
m_Caster = reader.ReadEntity<Mobile>();
m_End = reader.ReadDeltaTime();
m_Timer = new InternalTimer(this, m_End - Core.Now);
m_Timer.Start();
break;
}
}
}
public override bool OnMoveOver(Mobile m)
{
if (Visible && m_Caster != null && (!Core.AOS || m != m_Caster) &&
SpellHelper.ValidIndirectTarget(m_Caster, m) && m_Caster.CanBeHarmful(m, false))
{
if (SpellHelper.CanRevealCaster(m))
{
m_Caster.RevealingAction();
}
m_Caster.DoHarmful(m);
double duration;
if (Core.AOS)
{
duration = Math.Max(
2.0 + ((int)(m_Caster.Skills.EvalInt.Value / 10) - (int)(m.Skills.MagicResist.Value / 10)),
0.0
);
if (!m.Player)
{
duration *= 3.0;
}
}
else
{
duration = 7.0 + m_Caster.Skills.Magery.Value / 5;
}
m.Paralyze(TimeSpan.FromSeconds(duration));
m.PlaySound(0x204);
m.FixedEffect(0x376A, 10, 16);
(m as BaseCreature)?.OnHarmfulSpell(m_Caster);
}
return true;
}
private class InternalTimer : Timer
{
private readonly Item m_Item;
public InternalTimer(Item item, TimeSpan duration) : base(duration)
{
m_Item = item;
}
protected override void OnTick()
{
m_Item.Delete();
}
}
}
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
}
[DispellableField]
[SerializationGenerator(0, false)]
public partial class ParalyzeField : Item
{
[SerializableField(0)]
private Mobile _caster;
[DeltaDateTime]
[SerializableField(1)]
private DateTime _end;
private TimerExecutionToken _timer;
public ParalyzeField(Mobile caster, int itemID, Point3D loc, Map map, TimeSpan duration) : base(itemID)
{
Visible = false;
Movable = false;
Light = LightType.Circle300;
MoveToWorld(loc, map);
if (caster.InLOS(this))
{
Visible = true;
}
else
{
Delete();
}
if (Deleted)
{
return;
}
_caster = caster;
Timer.StartTimer(duration, Delete, out _timer);
_end = Core.Now + duration;
}
public override bool BlocksFit => true;
public override void OnAfterDelete()
{
base.OnAfterDelete();
_timer.Cancel();
}
[AfterDeserialization]
private void AfterDeserialization()
{
Timer.StartTimer(_end - Core.Now, Delete, out _timer);
}
public override bool OnMoveOver(Mobile m)
{
if (Visible && _caster != null && (!Core.AOS || m != _caster) &&
SpellHelper.ValidIndirectTarget(_caster, m) && _caster.CanBeHarmful(m, false))
{
if (SpellHelper.CanRevealCaster(m))
{
_caster.RevealingAction();
}
_caster.DoHarmful(m);
double duration;
if (Core.AOS)
{
duration = Math.Max(
2.0 + ((int)(_caster.Skills.EvalInt.Value / 10) - (int)(m.Skills.MagicResist.Value / 10)),
0.0
);
if (!m.Player)
{
duration *= 3.0;
}
}
else
{
duration = 7.0 + _caster.Skills.Magery.Value / 5;
}
m.Paralyze(TimeSpan.FromSeconds(duration));
m.PlaySound(0x204);
m.FixedEffect(0x376A, 10, 16);
(m as BaseCreature)?.OnHarmfulSpell(_caster);
}
return true;
}
}

View file

@ -164,8 +164,7 @@ namespace Server.Spells.Spellweaving
else // OSI renewal rules: the new one will override the old one, always.
{
to.SendLocalizedMessage(1072828); // Your arcane focus is renewed.
focus.LifeSpan = duration;
focus.CreationTime = Core.Now;
focus.ResetExpiration(duration);
focus.StrengthBonus = strengthBonus;
focus.InvalidateProperties();
focus.SendTimeRemainingMessage(to);

View file

@ -1,60 +1,40 @@
using System;
using ModernUO.Serialization;
namespace Server.Items
namespace Server.Items;
[SerializationGenerator(0, false)]
public partial class ArcaneFocus : TransientItem
{
public class ArcaneFocus : TransientItem
[SerializableField(0)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private int _strengthBonus;
[Constructible]
public ArcaneFocus() : this(TimeSpan.FromHours(1), 1)
{
[Constructible]
public ArcaneFocus()
: this(TimeSpan.FromHours(1), 1)
{
}
}
[Constructible]
public ArcaneFocus(int lifeSpan, int strengthBonus)
: this(TimeSpan.FromSeconds(lifeSpan), strengthBonus)
{
}
[Constructible]
public ArcaneFocus(int lifeSpan, int strengthBonus) : this(TimeSpan.FromSeconds(lifeSpan), strengthBonus)
{
}
public ArcaneFocus(TimeSpan lifeSpan, int strengthBonus) : base(0x3155, lifeSpan)
{
LootType = LootType.Blessed;
StrengthBonus = strengthBonus;
}
public ArcaneFocus(TimeSpan lifeSpan, int strengthBonus) : base(0x3155, lifeSpan)
{
LootType = LootType.Blessed;
_strengthBonus = strengthBonus;
}
public ArcaneFocus(Serial serial) : base(serial)
{
}
public override int LabelNumber => 1032629; // Arcane Focus
public override int LabelNumber => 1032629; // Arcane Focus
public override TextDefinition InvalidTransferMessage => 1073480; // Your arcane focus disappears.
public override bool Nontransferable => true;
[CommandProperty(AccessLevel.GameMaster)]
public int StrengthBonus { get; set; }
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
public override TextDefinition InvalidTransferMessage { get; } = 1073480; // Your arcane focus disappears.
public override bool Nontransferable => true;
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
list.Add(1060485, StrengthBonus); // strength bonus ~1_val~
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(StrengthBonus);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
StrengthBonus = reader.ReadInt();
}
list.Add(1060485, _strengthBonus); // strength bonus ~1_val~
}
}

View file

@ -1,105 +1,94 @@
using System;
using ModernUO.Serialization;
namespace Server.Items
namespace Server.Items;
[SerializationGenerator(1, false)]
public partial class TransientItem : Item
{
public class TransientItem : Item
private TimerExecutionToken _timerToken;
[DeltaDateTime]
[SerializableField(0)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private DateTime _expiration;
[Constructible]
public TransientItem(int itemID, TimeSpan lifeSpan) : base(itemID)
{
private TimerExecutionToken _timerToken;
_expiration = Core.Now + lifeSpan;
[Constructible]
public TransientItem(int itemID, TimeSpan lifeSpan)
: base(itemID)
Timer.StartTimer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), CheckExpiry, out _timerToken);
}
public override bool Nontransferable => true;
public virtual TextDefinition InvalidTransferMessage => TextDefinition.Empty;
public override void HandleInvalidTransfer(Mobile from)
{
InvalidTransferMessage.SendMessageTo(from);
Delete();
}
public virtual void Expire(Mobile parent)
{
parent?.SendLocalizedMessage(1072515, Name ?? $"#{LabelNumber}"); // The ~1_name~ expired...
Effects.PlaySound(GetWorldLocation(), Map, 0x201);
Delete();
}
public virtual void SendTimeRemainingMessage(Mobile to)
{
var remaining = Utility.Max(_expiration - Core.Now, TimeSpan.Zero);
to.SendLocalizedMessage(
1072516, // ~1_name~ will expire in ~2_val~ seconds!
$"{Name ?? $"#{LabelNumber}"}\t{remaining.TotalSeconds:F0}"
);
}
public override void OnDelete()
{
_timerToken.Cancel();
base.OnDelete();
}
public virtual void CheckExpiry()
{
if (_expiration - Core.Now <= TimeSpan.Zero)
{
CreationTime = Core.Now;
LifeSpan = lifeSpan;
Timer.StartTimer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), CheckExpiry, out _timerToken);
Expire(RootParent as Mobile);
}
public TransientItem(Serial serial)
: base(serial)
else
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TimeSpan LifeSpan { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DateTime CreationTime { get; set; }
public override bool Nontransferable => true;
public virtual TextDefinition InvalidTransferMessage => TextDefinition.Empty;
public override void HandleInvalidTransfer(Mobile from)
{
InvalidTransferMessage.SendMessageTo(from);
Delete();
}
public virtual void Expire(Mobile parent)
{
parent?.SendLocalizedMessage(1072515, Name ?? $"#{LabelNumber}"); // The ~1_name~ expired...
Effects.PlaySound(GetWorldLocation(), Map, 0x201);
Delete();
}
public virtual void SendTimeRemainingMessage(Mobile to)
{
to.SendLocalizedMessage(
1072516, // ~1_name~ will expire in ~2_val~ seconds!
$"{Name ?? $"#{LabelNumber}"}\t{LifeSpan.TotalSeconds:F0}"
);
}
public override void OnDelete()
{
_timerToken.Cancel();
base.OnDelete();
}
public virtual void CheckExpiry()
{
if (CreationTime + LifeSpan < Core.Now)
{
Expire(RootParent as Mobile);
}
else
{
InvalidateProperties();
}
}
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
var remaining = CreationTime + LifeSpan - Core.Now;
list.Add(1072517, $"{remaining.TotalSeconds:F0}"); // Lifespan: ~1_val~ seconds
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(LifeSpan);
writer.Write(CreationTime);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
LifeSpan = reader.ReadTimeSpan();
CreationTime = reader.ReadDateTime();
Timer.StartTimer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), CheckExpiry, out _timerToken);
InvalidateProperties();
}
}
public void ResetExpiration(TimeSpan duration) => Expiration = Core.Now + duration;
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
var remaining = Utility.Max(_expiration - Core.Now, TimeSpan.Zero);
list.Add(1072517, $"{remaining.TotalSeconds:F0}"); // Lifespan: ~1_val~ seconds
}
private void Deserialize(IGenericReader reader, int version)
{
var lifespan = reader.ReadTimeSpan();
var creationTime = reader.ReadDateTime(); // CreationTime
_expiration = creationTime + lifespan;
}
[AfterDeserialization]
private void AfterDeserialization()
{
Timer.StartTimer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), CheckExpiry, out _timerToken);
}
}

View file

@ -1,62 +1,48 @@
namespace Server.Mobiles
using ModernUO.Serialization;
namespace Server.Mobiles;
[SerializationGenerator(0, false)]
public partial class ArcaneFey : BaseCreature
{
public class ArcaneFey : BaseCreature
[Constructible]
public ArcaneFey() : base(AIType.AI_Mage, FightMode.Evil)
{
[Constructible]
public ArcaneFey() : base(AIType.AI_Mage, FightMode.Evil)
{
Name = NameList.RandomName("pixie");
Body = 128;
BaseSoundID = 0x467;
Name = NameList.RandomName("pixie");
Body = 128;
BaseSoundID = 0x467;
SetStr(20);
SetDex(150);
SetInt(125);
SetStr(20);
SetDex(150);
SetInt(125);
SetDamage(9, 15);
SetDamage(9, 15);
SetDamageType(ResistanceType.Physical, 100);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 80, 90);
SetResistance(ResistanceType.Fire, 40, 50);
SetResistance(ResistanceType.Cold, 40, 50);
SetResistance(ResistanceType.Poison, 40, 50);
SetResistance(ResistanceType.Energy, 40, 50);
SetResistance(ResistanceType.Physical, 80, 90);
SetResistance(ResistanceType.Fire, 40, 50);
SetResistance(ResistanceType.Cold, 40, 50);
SetResistance(ResistanceType.Poison, 40, 50);
SetResistance(ResistanceType.Energy, 40, 50);
SetSkill(SkillName.EvalInt, 70.1, 80.0);
SetSkill(SkillName.Magery, 70.1, 80.0);
SetSkill(SkillName.Meditation, 70.1, 80.0);
SetSkill(SkillName.MagicResist, 50.5, 100.0);
SetSkill(SkillName.Tactics, 10.1, 20.0);
SetSkill(SkillName.Wrestling, 10.1, 12.5);
SetSkill(SkillName.EvalInt, 70.1, 80.0);
SetSkill(SkillName.Magery, 70.1, 80.0);
SetSkill(SkillName.Meditation, 70.1, 80.0);
SetSkill(SkillName.MagicResist, 50.5, 100.0);
SetSkill(SkillName.Tactics, 10.1, 20.0);
SetSkill(SkillName.Wrestling, 10.1, 12.5);
Fame = 0;
Karma = 0;
Fame = 0;
Karma = 0;
ControlSlots = 1;
}
public ArcaneFey(Serial serial) : base(serial)
{
}
public override string CorpseName => "a pixie corpse";
public override double DispelDifficulty => 70.0;
public override double DispelFocus => 20.0;
public override OppositionGroup OppositionGroup => OppositionGroup.FeyAndUndead;
public override bool InitialInnocent => true;
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
}
ControlSlots = 1;
}
public override string CorpseName => "a pixie corpse";
public override double DispelDifficulty => 70.0;
public override double DispelFocus => 20.0;
public override OppositionGroup OppositionGroup => OppositionGroup.FeyAndUndead;
public override bool InitialInnocent => true;
}

View file

@ -1,63 +1,49 @@
namespace Server.Mobiles
using ModernUO.Serialization;
namespace Server.Mobiles;
[SerializationGenerator(0, false)]
public partial class ArcaneFiend : BaseCreature
{
public class ArcaneFiend : BaseCreature
[Constructible]
public ArcaneFiend() : base(AIType.AI_Mage)
{
[Constructible]
public ArcaneFiend() : base(AIType.AI_Mage)
{
Body = 74;
BaseSoundID = 422;
Body = 74;
BaseSoundID = 422;
SetStr(55);
SetDex(40);
SetInt(60);
SetStr(55);
SetDex(40);
SetInt(60);
SetDamage(10, 14);
SetDamage(10, 14);
SetDamageType(ResistanceType.Physical, 0);
SetDamageType(ResistanceType.Fire, 50);
SetDamageType(ResistanceType.Poison, 50);
SetDamageType(ResistanceType.Physical, 0);
SetDamageType(ResistanceType.Fire, 50);
SetDamageType(ResistanceType.Poison, 50);
SetResistance(ResistanceType.Physical, 25, 35);
SetResistance(ResistanceType.Fire, 40, 50);
SetResistance(ResistanceType.Cold, 20, 30);
SetResistance(ResistanceType.Poison, 30, 40);
SetResistance(ResistanceType.Energy, 30, 40);
SetResistance(ResistanceType.Physical, 25, 35);
SetResistance(ResistanceType.Fire, 40, 50);
SetResistance(ResistanceType.Cold, 20, 30);
SetResistance(ResistanceType.Poison, 30, 40);
SetResistance(ResistanceType.Energy, 30, 40);
SetSkill(SkillName.EvalInt, 20.1, 30.0);
SetSkill(SkillName.Magery, 60.1, 70.0);
SetSkill(SkillName.MagicResist, 30.1, 50.0);
SetSkill(SkillName.Tactics, 42.1, 50.0);
SetSkill(SkillName.Wrestling, 40.1, 44.0);
SetSkill(SkillName.EvalInt, 20.1, 30.0);
SetSkill(SkillName.Magery, 60.1, 70.0);
SetSkill(SkillName.MagicResist, 30.1, 50.0);
SetSkill(SkillName.Tactics, 42.1, 50.0);
SetSkill(SkillName.Wrestling, 40.1, 44.0);
Fame = 0;
Karma = 0;
Fame = 0;
Karma = 0;
ControlSlots = 1;
}
public ArcaneFiend(Serial serial) : base(serial)
{
}
public override string CorpseName => "an imp corpse";
public override double DispelDifficulty => 70.0;
public override double DispelFocus => 20.0;
public override PackInstinct PackInstinct => PackInstinct.Daemon;
public override bool BleedImmune => true; // TODO: Verify on OSI. Guide says this.
public override string DefaultName => "an imp";
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
}
ControlSlots = 1;
}
public override string CorpseName => "an imp corpse";
public override double DispelDifficulty => 70.0;
public override double DispelFocus => 20.0;
public override PackInstinct PackInstinct => PackInstinct.Daemon;
public override bool BleedImmune => true; // TODO: Verify on OSI. Guide says this.
public override string DefaultName => "an imp";
}

View file

@ -1,86 +1,74 @@
using System;
using ModernUO.Serialization;
namespace Server.Mobiles
namespace Server.Mobiles;
[SerializationGenerator(0, false)]
public partial class NatureFury : BaseCreature
{
public class NatureFury : BaseCreature
[Constructible]
public NatureFury() : base(AIType.AI_Melee)
{
[Constructible]
public NatureFury() : base(AIType.AI_Melee)
Body = 0x33;
Hue = 0x4001;
SetStr(150);
SetDex(150);
SetInt(100);
SetHits(80);
SetStam(250);
SetMana(0);
SetDamage(6, 8);
SetDamageType(ResistanceType.Poison, 100);
SetDamageType(ResistanceType.Physical, 0);
SetResistance(ResistanceType.Physical, 90);
SetSkill(SkillName.Wrestling, 90.0);
SetSkill(SkillName.MagicResist, 70.0);
SetSkill(SkillName.Tactics, 100.0);
Fame = 0;
Karma = 0;
ControlSlots = 1;
}
public override bool DeleteCorpseOnDeath => Core.AOS;
public override bool IsHouseSummonable => true;
public override double DispelDifficulty => 125.0;
public override double DispelFocus => 90.0;
public override bool BleedImmune => true;
public override Poison PoisonImmune => Poison.Lethal;
public override bool AlwaysMurderer => true;
public override string DefaultName => "a nature's fury";
public override void MoveToWorld(Point3D loc, Map map)
{
base.MoveToWorld(loc, map);
Timer.StartTimer(DoEffects);
}
public void DoEffects()
{
FixedParticles(0x91C, 10, 180, 0x2543, 0, 0, EffectLayer.Waist);
PlaySound(0xE);
PlaySound(0x1BC);
if (Alive && !Deleted)
{
Body = 0x33;
Hue = 0x4001;
SetStr(150);
SetDex(150);
SetInt(100);
SetHits(80);
SetStam(250);
SetMana(0);
SetDamage(6, 8);
SetDamageType(ResistanceType.Poison, 100);
SetDamageType(ResistanceType.Physical, 0);
SetResistance(ResistanceType.Physical, 90);
SetSkill(SkillName.Wrestling, 90.0);
SetSkill(SkillName.MagicResist, 70.0);
SetSkill(SkillName.Tactics, 100.0);
Fame = 0;
Karma = 0;
ControlSlots = 1;
}
public NatureFury(Serial serial)
: base(serial)
{
}
public override bool DeleteCorpseOnDeath => Core.AOS;
public override bool IsHouseSummonable => true;
public override double DispelDifficulty => 125.0;
public override double DispelFocus => 90.0;
public override bool BleedImmune => true;
public override Poison PoisonImmune => Poison.Lethal;
public override bool AlwaysMurderer => true;
public override string DefaultName => "a nature's fury";
public override void MoveToWorld(Point3D loc, Map map)
{
base.MoveToWorld(loc, map);
Timer.StartTimer(DoEffects);
}
public void DoEffects()
{
FixedParticles(0x91C, 10, 180, 0x2543, 0, 0, EffectLayer.Waist);
PlaySound(0xE);
PlaySound(0x1BC);
if (Alive && !Deleted)
{
Timer.StartTimer(TimeSpan.FromSeconds(7.0), DoEffects);
}
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
Delete();
Timer.StartTimer(TimeSpan.FromSeconds(7.0), DoEffects);
}
}
[AfterDeserialization(false)]
private void AfterDeserialization()
{
Delete();
}
}

View file

@ -100,8 +100,7 @@ namespace Server.Spells.Third
using var queue = PooledRefQueue<Item>.Create();
foreach (var item in m.GetItemsAt())
{
if (item is ParalyzeFieldSpell.InternalItem or
PoisonFieldSpell.InternalItem or FireFieldSpell.FireFieldItem)
if (item is ParalyzeField or PoisonField or FireFieldItem)
{
// Use a queue just in case OnMoveOver changes the item's sector
queue.Enqueue(item);

View file

@ -1,186 +1,137 @@
using System;
using ModernUO.Serialization;
using Server.Misc;
using Server.Mobiles;
namespace Server.Spells.Third
namespace Server.Spells.Third;
public class WallOfStoneSpell : MagerySpell, ISpellTargetingPoint3D
{
public class WallOfStoneSpell : MagerySpell, ISpellTargetingPoint3D
private static readonly SpellInfo _info = new(
"Wall of Stone",
"In Sanct Ylem",
227,
9011,
false,
Reagent.Bloodmoss,
Reagent.Garlic
);
public WallOfStoneSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
private static readonly SpellInfo _info = new(
"Wall of Stone",
"In Sanct Ylem",
227,
9011,
false,
Reagent.Bloodmoss,
Reagent.Garlic
);
}
public WallOfStoneSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
public override SpellCircle Circle => SpellCircle.Third;
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x1F6);
for (var i = -1; i <= 1; ++i)
{
var targetLoc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z);
var canFit = SpellHelper.AdjustField(ref targetLoc, Caster.Map, 22, true);
// Effects.SendLocationParticles( EffectItem.Create( loc, Caster.Map, EffectItem.DefaultDuration ), 0x376A, 9, 10, 5025 );
if (!canFit)
{
continue;
}
Item item = new WallOfStone(targetLoc, Caster.Map, Caster);
Effects.SendLocationParticles(item, 0x376A, 9, 10, 5025);
// new WallOfStone( loc, Caster.Map, Caster );
}
}
public override SpellCircle Circle => SpellCircle.Third;
FinishSequence();
}
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
SpellHelper.GetSurfaceTop(ref p);
var loc = new Point3D(p);
var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc);
Effects.PlaySound(loc, Caster.Map, 0x1F6);
for (var i = -1; i <= 1; ++i)
{
var targetLoc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z);
var canFit = SpellHelper.AdjustField(ref targetLoc, Caster.Map, 22, true);
// Effects.SendLocationParticles( EffectItem.Create( loc, Caster.Map, EffectItem.DefaultDuration ), 0x376A, 9, 10, 5025 );
if (!canFit)
{
continue;
}
Item item = new InternalItem(targetLoc, Caster.Map, Caster);
Effects.SendLocationParticles(item, 0x376A, 9, 10, 5025);
// new InternalItem( loc, Caster.Map, Caster );
}
}
FinishSequence();
}
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
[DispellableField]
private class InternalItem : Item
{
private readonly Mobile m_Caster;
private DateTime m_End;
private Timer m_Timer;
public InternalItem(Point3D loc, Map map, Mobile caster) : base(0x82)
{
Visible = false;
Movable = false;
MoveToWorld(loc, map);
m_Caster = caster;
if (caster.InLOS(this))
{
Visible = true;
}
else
{
Delete();
}
if (Deleted)
{
return;
}
m_Timer = new InternalTimer(this, TimeSpan.FromSeconds(10.0));
m_Timer.Start();
m_End = Core.Now + TimeSpan.FromSeconds(10.0);
}
public InternalItem(Serial serial) : base(serial)
{
}
public override bool BlocksFit => true;
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
writer.WriteDeltaTime(m_End);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
switch (version)
{
case 1:
{
m_End = reader.ReadDeltaTime();
m_Timer = new InternalTimer(this, m_End - Core.Now);
m_Timer.Start();
break;
}
case 0:
{
var duration = TimeSpan.FromSeconds(10.0);
m_Timer = new InternalTimer(this, duration);
m_Timer.Start();
m_End = Core.Now + duration;
break;
}
}
}
public override bool OnMoveOver(Mobile m)
{
if (m is PlayerMobile)
{
var noto = Notoriety.Compute(m_Caster, m);
if (noto is Notoriety.Enemy or Notoriety.Ally)
{
return false;
}
}
return base.OnMoveOver(m);
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Timer?.Stop();
}
private class InternalTimer : Timer
{
private readonly InternalItem m_Item;
public InternalTimer(InternalItem item, TimeSpan duration) : base(duration)
{
m_Item = item;
}
protected override void OnTick()
{
m_Item.Delete();
}
}
}
public override void OnCast()
{
Caster.Target = new SpellTargetPoint3D(this, range: Core.ML ? 10 : 12);
}
}
[DispellableField]
[SerializationGenerator(0, false)]
public partial class WallOfStone : Item
{
[SerializableField(0)]
private Mobile _caster;
[DeltaDateTime]
[SerializableField(1)]
private DateTime _end;
private TimerExecutionToken _timer;
public WallOfStone(Point3D loc, Map map, Mobile caster) : base(0x82)
{
Visible = false;
Movable = false;
MoveToWorld(loc, map);
_caster = caster;
if (caster.InLOS(this))
{
Visible = true;
}
else
{
Delete();
}
if (Deleted)
{
return;
}
var duration = TimeSpan.FromSeconds(10.0);
Timer.StartTimer(duration, Delete, out _timer);
_end = Core.Now + duration;
}
public override bool BlocksFit => true;
public override bool OnMoveOver(Mobile m)
{
if (m is PlayerMobile)
{
var noto = Notoriety.Compute(_caster, m);
if (noto is Notoriety.Enemy or Notoriety.Ally)
{
return false;
}
}
return base.OnMoveOver(m);
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
_timer.Cancel();
}
[AfterDeserialization]
private void AfterDeserialization()
{
Timer.StartTimer(_end - Core.Now, Delete, out _timer);
}
}