diff --git a/Projects/UOContent/Items/Misc/Firebomb.cs b/Projects/UOContent/Items/Misc/Firebomb.cs index 2916ec80f..de846f3ca 100644 --- a/Projects/UOContent/Items/Misc/Firebomb.cs +++ b/Projects/UOContent/Items/Misc/Firebomb.cs @@ -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); } } diff --git a/Projects/UOContent/Migrations/Server.Items.ArcaneFocus.v0.json b/Projects/UOContent/Migrations/Server.Items.ArcaneFocus.v0.json new file mode 100644 index 000000000..2feafc27b --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.ArcaneFocus.v0.json @@ -0,0 +1,14 @@ +{ + "version": 0, + "type": "Server.Items.ArcaneFocus", + "properties": [ + { + "name": "StrengthBonus", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.TransientItem.v1.json b/Projects/UOContent/Migrations/Server.Items.TransientItem.v1.json new file mode 100644 index 000000000..2f63e0bde --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.TransientItem.v1.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "type": "Server.Items.TransientItem", + "properties": [ + { + "name": "Expiration", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.ArcaneFey.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.ArcaneFey.v0.json new file mode 100644 index 000000000..21ee48841 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.ArcaneFey.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.ArcaneFey" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.ArcaneFiend.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.ArcaneFiend.v0.json new file mode 100644 index 000000000..1bb9cf05a --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.ArcaneFiend.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.ArcaneFiend" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.NatureFury.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.NatureFury.v0.json new file mode 100644 index 000000000..b77c6853b --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.NatureFury.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.NatureFury" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Fifth.PoisonField.v0.json b/Projects/UOContent/Migrations/Server.Spells.Fifth.PoisonField.v0.json new file mode 100644 index 000000000..62876e8d9 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Fifth.PoisonField.v0.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Fourth.FireFieldItem.v0.json b/Projects/UOContent/Migrations/Server.Spells.Fourth.FireFieldItem.v0.json new file mode 100644 index 000000000..b99086ec2 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Fourth.FireFieldItem.v0.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Seventh.EnergyField.v1.json b/Projects/UOContent/Migrations/Server.Spells.Seventh.EnergyField.v1.json new file mode 100644 index 000000000..b5e81cb85 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Seventh.EnergyField.v1.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Seventh.GateTravelMoongate.v0.json b/Projects/UOContent/Migrations/Server.Spells.Seventh.GateTravelMoongate.v0.json new file mode 100644 index 000000000..84e654f6a --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Seventh.GateTravelMoongate.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Spells.Seventh.GateTravelMoongate" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Sixth.ParalyzeField.v0.json b/Projects/UOContent/Migrations/Server.Spells.Sixth.ParalyzeField.v0.json new file mode 100644 index 000000000..7d6cd2e3a --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Sixth.ParalyzeField.v0.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Third.WallOfStone.v0.json b/Projects/UOContent/Migrations/Server.Spells.Third.WallOfStone.v0.json new file mode 100644 index 000000000..19cf3ec39 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Third.WallOfStone.v0.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Spells/Fifth/PoisonField.cs b/Projects/UOContent/Spells/Fifth/PoisonField.cs index e5b2fbdf5..65225dbaf 100644 --- a/Projects/UOContent/Spells/Fifth/PoisonField.cs +++ b/Projects/UOContent/Spells/Fifth/PoisonField.cs @@ -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(); - - 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.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.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); } } } diff --git a/Projects/UOContent/Spells/Fourth/FireField.cs b/Projects/UOContent/Spells/Fourth/FireField.cs index 0bf48268d..9899f5291 100644 --- a/Projects/UOContent/Spells/Fourth/FireField.cs +++ b/Projects/UOContent/Spells/Fourth/FireField.cs @@ -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(); - - 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.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.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); } } } diff --git a/Projects/UOContent/Spells/Seventh/EnergyField.cs b/Projects/UOContent/Spells/Seventh/EnergyField.cs index 988d19fb1..55cbf1540 100644 --- a/Projects/UOContent/Spells/Seventh/EnergyField.cs +++ b/Projects/UOContent/Spells/Seventh/EnergyField.cs @@ -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(); } } diff --git a/Projects/UOContent/Spells/Seventh/GateTravel.cs b/Projects/UOContent/Spells/Seventh/GateTravel.cs index 40b33ca07..c113b9708 100644 --- a/Projects/UOContent/Spells/Seventh/GateTravel.cs +++ b/Projects/UOContent/Spells/Seventh/GateTravel.cs @@ -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(); } } diff --git a/Projects/UOContent/Spells/Sixth/ParalyzeField.cs b/Projects/UOContent/Spells/Sixth/ParalyzeField.cs index ac20ce7dd..baf1df872 100644 --- a/Projects/UOContent/Spells/Sixth/ParalyzeField.cs +++ b/Projects/UOContent/Spells/Sixth/ParalyzeField.cs @@ -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(); - 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; } } diff --git a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs index 8becc9600..dbd5aef66 100644 --- a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs +++ b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs @@ -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); diff --git a/Projects/UOContent/Spells/Spellweaving/Items/ArcaneFocus.cs b/Projects/UOContent/Spells/Spellweaving/Items/ArcaneFocus.cs index fd51c150e..761001da3 100644 --- a/Projects/UOContent/Spells/Spellweaving/Items/ArcaneFocus.cs +++ b/Projects/UOContent/Spells/Spellweaving/Items/ArcaneFocus.cs @@ -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~ } } diff --git a/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs b/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs index 300bcf106..54ac52030 100644 --- a/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs +++ b/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs @@ -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); + } } diff --git a/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFey.cs b/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFey.cs index 3f1defbde..0ffc2e7b7 100644 --- a/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFey.cs +++ b/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFey.cs @@ -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; } diff --git a/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFiend.cs b/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFiend.cs index 6041979af..bd0f38712 100644 --- a/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFiend.cs +++ b/Projects/UOContent/Spells/Spellweaving/Mobiles/ArcaneFiend.cs @@ -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"; } diff --git a/Projects/UOContent/Spells/Spellweaving/Mobiles/NatureFury.cs b/Projects/UOContent/Spells/Spellweaving/Mobiles/NatureFury.cs index d91aa4318..a1deba44f 100644 --- a/Projects/UOContent/Spells/Spellweaving/Mobiles/NatureFury.cs +++ b/Projects/UOContent/Spells/Spellweaving/Mobiles/NatureFury.cs @@ -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(); + } } diff --git a/Projects/UOContent/Spells/Third/Teleport.cs b/Projects/UOContent/Spells/Third/Teleport.cs index b7c21bcf2..78860e5da 100644 --- a/Projects/UOContent/Spells/Third/Teleport.cs +++ b/Projects/UOContent/Spells/Third/Teleport.cs @@ -100,8 +100,7 @@ namespace Server.Spells.Third using var queue = PooledRefQueue.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); diff --git a/Projects/UOContent/Spells/Third/WallOfStone.cs b/Projects/UOContent/Spells/Third/WallOfStone.cs index 094374a86..d5037a9f6 100644 --- a/Projects/UOContent/Spells/Third/WallOfStone.cs +++ b/Projects/UOContent/Spells/Third/WallOfStone.cs @@ -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); } }