fix: Fixes skill requirements and cleans up magery spells (#870)

* Preps damage stacking for mysticism, necromancy fixes, and spellweaving
* Removes extra LINQ allocations
* Fixes skill requirements for magery spells.
This commit is contained in:
Kamron Batman 2021-12-05 09:40:57 -08:00 committed by GitHub
parent 1cf7b6c056
commit 4f9714818d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 311 additions and 382 deletions

View file

@ -5,9 +5,10 @@ namespace Server.Spells
{
public abstract class MagerySpell : Spell
{
private const double ChanceOffset = 20.0, ChanceLength = 100.0 / 7.0;
private static readonly int[] m_ManaTable = { 4, 6, 9, 11, 14, 20, 40, 50 };
private static readonly int[] _manaTable = { 4, 6, 9, 11, 14, 20, 40, 50 };
private static readonly double[] _requiredSkill = Core.ML ?
new[] { 0.0, -4.0, 10.0, 24.0, 38.0, 52.0, 66.0, 80.0 } :
new[] { 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0 };
public MagerySpell(Mobile caster, Item scroll, SpellInfo info) : base(caster, scroll, info)
{
@ -29,13 +30,20 @@ namespace Server.Spells
circle -= 2;
}
var avg = ChanceLength * circle;
// Original RunUO algorithm for required skill
// const double chanceOffset = 20.0
// const double chanceLength = 100.0 / 7.0
// var avg = chanceLength * circle;
// min = avg - chanceOffset;
// max = avg + chanceOffset;
min = avg - ChanceOffset;
max = avg + ChanceOffset;
// Correct algorithm according to OSI.
// TODO: Verify this algorithm on OSI for latest expansion.
min = _requiredSkill[circle];
max = min + 40;
}
public override int GetMana() => Scroll is BaseWand ? 0 : m_ManaTable[(int)Circle];
public override int GetMana() => Scroll is BaseWand ? 0 : _manaTable[(int)Circle];
public override double GetResistSkill(Mobile m)
{
@ -79,12 +87,13 @@ namespace Server.Spells
public virtual double GetResistPercentForCircle(Mobile target, SpellCircle circle)
{
var firstPercent = target.Skills.MagicResist.Value / 5.0;
var secondPercent = target.Skills.MagicResist.Value -
var magicResist = target.Skills.MagicResist.Value;
var firstPercent = magicResist / 5.0;
var secondPercent = magicResist -
((Caster.Skills[CastSkill].Value - 20.0) / 5.0 + (1 + (int)circle) * 5.0);
return (firstPercent > secondPercent ? firstPercent : secondPercent) /
2.0; // Seems should be about half of what stratics says.
// Seems should be about half of what stratics says.
return (firstPercent > secondPercent ? firstPercent : secondPercent) / 2.0;
}
public virtual double GetResistPercent(Mobile target) => GetResistPercentForCircle(target, Circle);

View file

@ -10,8 +10,7 @@ namespace Server.Spells
{
public abstract class SpecialMove
{
private static readonly Dictionary<Mobile, SpecialMoveContext> m_PlayersTable =
new();
private static readonly Dictionary<Mobile, SpecialMoveContext> _playersTable = new();
public virtual int BaseMana => 0;
@ -159,40 +158,18 @@ namespace Server.Spells
return false;
}
string option = null;
if (this is Backstab)
string option = this switch
{
option = "Backstab";
}
else if (this is DeathStrike)
{
option = "Death Strike";
}
else if (this is FocusAttack)
{
option = "Focus Attack";
}
else if (this is KiAttack)
{
option = "Ki Attack";
}
else if (this is SurpriseAttack)
{
option = "Surprise Attack";
}
else if (this is HonorableExecution)
{
option = "Honorable Execution";
}
else if (this is LightningStrike)
{
option = "Lightning Strike";
}
else if (this is MomentumStrike)
{
option = "Momentum Strike";
}
Backstab => "Backstab",
DeathStrike => "Death Strike",
FocusAttack => "Focus Attack",
KiAttack => "Ki Attack",
SurpriseAttack => "Surprise Attack",
HonorableExecution => "Honorable Execution",
LightningStrike => "Lightning Strike",
MomentumStrike => "Momentum Strike",
_ => null
};
if (option != null && !DuelContext.AllowSpecialMove(from, option, this))
{
@ -303,7 +280,7 @@ namespace Server.Spells
private static void AddContext(Mobile m, SpecialMoveContext context)
{
m_PlayersTable[m] = context;
_playersTable[m] = context;
}
private static void RemoveContext(Mobile m)
@ -312,23 +289,20 @@ namespace Server.Spells
if (context != null)
{
m_PlayersTable.Remove(m);
_playersTable.Remove(m);
context.Timer.Stop();
}
}
private static SpecialMoveContext GetContext(Mobile m) =>
m_PlayersTable.TryGetValue(m, out var context) ? context : null;
_playersTable.TryGetValue(m, out var context) ? context : null;
private class SpecialMoveTimer : Timer
{
private readonly Mobile m_Mobile;
public SpecialMoveTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0))
{
m_Mobile = from;
}
public SpecialMoveTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0)) => m_Mobile = from;
protected override void OnTick()
{

View file

@ -17,19 +17,16 @@ namespace Server.Spells
public abstract class Spell : ISpell
{
private static readonly TimeSpan NextSpellDelay = TimeSpan.FromSeconds(0.75);
private static readonly TimeSpan AnimateDelay = TimeSpan.FromSeconds(1.5);
// In reality, it's ANY delayed Damage spell Post-AoS that can't stack, but, only
// Expo & Magic Arrow have enough delay and a short enough cast time to bring up
// the possibility of stacking 'em. Note that a MA & an Explosion will stack, but
// of course, two MA's won't.
private static readonly Dictionary<Type, DelayedDamageContextWrapper> m_ContextTable =
new();
private static readonly Dictionary<Type, DelayedDamageContextWrapper> _contextTable = new();
private AnimTimer m_AnimTimer;
private CastTimer m_CastTimer;
private AnimTimer _animTimer;
private CastTimer _castTimer;
public Spell(Mobile caster, Item scroll, SpellInfo info)
{
@ -60,13 +57,17 @@ namespace Server.Spells
public virtual bool DelayedDamage => false;
public virtual bool DelayedDamageStacking => true;
public static readonly Type[] AOSNoDelayedDamageStackingSelf = Core.AOS ? Array.Empty<Type>() : null;
// Null means stacking is allowed while empty indicates no stacking with self
// More than zero means no stacking with self and other spells
public virtual Type[] DelayedDamageSpellFamilyStacking => null;
public virtual bool BlockedByHorrificBeast => true;
public virtual bool BlockedByAnimalForm => true;
public virtual bool BlocksMovement => true;
public virtual bool CheckNextSpellTime => !(Scroll is BaseWand);
public virtual bool CheckNextSpellTime => Scroll is not BaseWand;
public virtual int CastRecoveryBase => 6;
public virtual int CastRecoveryFastScalar => 1;
@ -149,22 +150,39 @@ namespace Server.Spells
public void StartDelayedDamageContext(Mobile m, Timer t)
{
if (DelayedDamageStacking)
var damageStacking = DelayedDamageSpellFamilyStacking;
if (damageStacking == null)
{
return; // Sanity
}
if (!m_ContextTable.TryGetValue(GetType(), out var contexts))
var type = GetType();
if (!_contextTable.TryGetValue(type, out var context))
{
m_ContextTable[GetType()] = contexts = new DelayedDamageContextWrapper();
_contextTable[type] = context = new DelayedDamageContextWrapper();
for (int i = 0; i < damageStacking.Length; i++)
{
_contextTable.Add(damageStacking[i], context);
}
}
contexts.Add(m, t);
context.Add(m, t);
}
public bool HasDelayedDamageContext(Mobile m) =>
DelayedDamageSpellFamilyStacking != null &&
_contextTable.TryGetValue(GetType(), out var context) && context.Contains(m);
public void RemoveDelayedDamageContext(Mobile m)
{
if (m_ContextTable.TryGetValue(GetType(), out var contexts))
if (m == null || DelayedDamageSpellFamilyStacking == null)
{
return; // Sanity
}
if (_contextTable.TryGetValue(GetType(), out var contexts))
{
contexts.Remove(m);
}
@ -389,8 +407,8 @@ namespace Server.Spells
{
OnDisturb(type, true);
m_CastTimer?.Stop();
m_AnimTimer?.Stop();
_castTimer?.Stop();
_animTimer?.Stop();
if (Core.AOS && Caster.Player && type == DisturbType.Hurt)
{
@ -469,7 +487,7 @@ namespace Server.Spells
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
}
else if (!(Scroll is BaseWand) && (Caster.Paralyzed || Caster.Frozen))
else if (Scroll is not BaseWand && (Caster.Paralyzed || Caster.Frozen))
{
Caster.SendLocalizedMessage(502643); // You can not cast a spell while frozen.
}
@ -492,7 +510,7 @@ namespace Server.Spells
State = SpellState.Casting;
Caster.Spell = this;
if (!(Scroll is BaseWand) && RevealOnCast)
if (Scroll is not BaseWand && RevealOnCast)
{
Caster.RevealingAction();
}
@ -507,8 +525,8 @@ namespace Server.Spells
if (count != 0)
{
m_AnimTimer = new AnimTimer(this, count);
m_AnimTimer.Start();
_animTimer = new AnimTimer(this, count);
_animTimer.Start();
}
if (Info.LeftHandEffect > 0)
@ -532,18 +550,18 @@ namespace Server.Spells
WeaponAbility.ClearCurrentAbility(Caster);
}
m_CastTimer = new CastTimer(this, castDelay);
_castTimer = new CastTimer(this, castDelay);
// m_CastTimer.Start();
OnBeginCast();
if (castDelay > TimeSpan.Zero)
{
m_CastTimer.Start();
_castTimer.Start();
}
else
{
m_CastTimer.Tick();
_castTimer.Tick();
}
return true;
@ -647,11 +665,6 @@ namespace Server.Spells
return TimeSpan.FromSeconds((double)delay / CastRecoveryPerSecond);
}
// public virtual int CastDelayBase{ get{ return 3; } }
// public virtual int CastDelayFastScalar{ get{ return 1; } }
// public virtual int CastDelayPerSecond{ get{ return 4; } }
// public virtual int CastDelayMinimum{ get{ return 1; } }
public virtual TimeSpan GetCastDelay()
{
if (Scroll is BaseWand)
@ -665,7 +678,7 @@ namespace Server.Spells
// Paladins with magery of 70.0 or above are subject to a faster casting cap of 2
var fcMax = 4;
if (CastSkill == SkillName.Magery || CastSkill == SkillName.Necromancy ||
if (CastSkill is SkillName.Magery or SkillName.Necromancy ||
CastSkill == SkillName.Chivalry && Caster.Skills.Magery.Value >= 70.0)
{
fcMax = 2;
@ -749,12 +762,9 @@ namespace Server.Spells
Scroll.Movable = m;
}
else
else if (ClearHandsOnCast)
{
if (ClearHandsOnCast)
{
Caster.ClearHands();
}
Caster.ClearHands();
}
var karma = ComputeKarmaAward();
@ -838,10 +848,14 @@ namespace Server.Spells
m_Contexts.Add(m, t);
}
public bool Contains(Mobile m) => m_Contexts.ContainsKey(m);
public void Remove(Mobile m)
{
m_Contexts.Remove(m);
// TODO: Should we stop the timer?
if (m_Contexts.Remove(m, out var t))
{
t.Stop();
}
}
}
@ -876,7 +890,7 @@ namespace Server.Spells
if (!Running)
{
m_Spell.m_AnimTimer = null;
m_Spell._animTimer = null;
}
}
}
@ -900,7 +914,7 @@ namespace Server.Spells
if (m_Spell.State == SpellState.Casting && m_Spell.Caster.Spell == m_Spell)
{
m_Spell.State = SpellState.Sequencing;
m_Spell.m_CastTimer = null;
m_Spell._castTimer = null;
m_Spell.Caster.OnSpellCast(m_Spell);
m_Spell.Caster.Region?.OnSpellCast(m_Spell.Caster, m_Spell);
m_Spell.Caster.NextSpellTime =
@ -915,7 +929,7 @@ namespace Server.Spells
m_Spell.Caster.Target?.BeginTimeout(m_Spell.Caster, 30000); // 30 seconds
}
m_Spell.m_CastTimer = null;
m_Spell._castTimer = null;
}
}

View file

@ -184,7 +184,7 @@ namespace Server.Spells
public static void Turn(Mobile from, object to)
{
if (!(to is IPoint3D target))
if (to is not IPoint3D target)
{
return;
}
@ -333,7 +333,7 @@ namespace Server.Spells
public static bool AddStatCurse(Mobile caster, Mobile target, StatType type, int curse, TimeSpan duration)
{
var offset = -curse;
var name = $"[Magic] {type} Offset";
var name = $"[Magic] {type} Curse";
var mod = target.GetStatMod(name);
@ -447,26 +447,8 @@ namespace Server.Spells
var bcFrom = from as BaseCreature;
var bcTarg = to as BaseCreature;
PlayerMobile pmFrom;
PlayerMobile pmTarg;
if (bcFrom?.Summoned == true)
{
pmFrom = bcFrom.SummonMaster as PlayerMobile;
}
else
{
pmFrom = from as PlayerMobile;
}
if (bcTarg?.Summoned == true)
{
pmTarg = bcTarg.SummonMaster as PlayerMobile;
}
else
{
pmTarg = to as PlayerMobile;
}
var pmFrom = (bcFrom?.Summoned == true ? bcFrom.SummonMaster : from) as PlayerMobile;
var pmTarg = (bcTarg?.Summoned == true ? bcTarg.SummonMaster : to) as PlayerMobile;
if (pmFrom?.DuelContext != null && pmFrom.DuelContext == pmTarg?.DuelContext && pmFrom.DuelContext.Started &&
pmFrom.DuelPlayer != null && pmTarg?.DuelPlayer != null)
@ -864,17 +846,6 @@ namespace Server.Spells
return x < 0 || y < 0 || x >= map.Width || y >= map.Height;
}
// towns
public static bool IsTown(IPoint3D ip, Mobile caster)
{
if (ip is Item item)
{
ip = item.GetWorldLocation();
}
return IsTown(new Point3D(ip), caster);
}
public static bool IsTown(Point3D loc, Mobile caster)
{
var map = caster.Map;
@ -897,15 +868,8 @@ namespace Server.Spells
return reg?.IsDisabled() == false;
}
public static bool CheckTown(IPoint3D ip, Mobile caster)
{
if (ip is Item item)
{
ip = item.GetWorldLocation();
}
return CheckTown(new Point3D(ip), caster);
}
public static bool CheckTown(IPoint3D ip, Mobile caster) =>
CheckTown((ip as Item)?.GetWorldLocation() ?? new Point3D(ip), caster);
public static bool CheckTown(Point3D loc, Mobile caster)
{
@ -919,13 +883,13 @@ namespace Server.Spells
}
// magic reflection
public static void CheckReflect(int circle, Mobile caster, ref Mobile target)
{
public static bool CheckReflect(int circle, Mobile caster, ref Mobile target) =>
CheckReflect(circle, ref caster, ref target);
}
public static void CheckReflect(int circle, ref Mobile caster, ref Mobile target)
public static bool CheckReflect(int circle, ref Mobile caster, ref Mobile target)
{
var reflect = false;
if (target.MagicDamageAbsorb > 0)
{
++circle;
@ -933,41 +897,26 @@ namespace Server.Spells
target.MagicDamageAbsorb -= circle;
// This order isn't very intuitive, but you have to nullify reflect before target gets switched
var reflect = target.MagicDamageAbsorb >= 0;
(target as BaseCreature)?.CheckReflect(caster, ref reflect);
reflect = target.MagicDamageAbsorb >= 0;
if (target.MagicDamageAbsorb <= 0)
{
target.MagicDamageAbsorb = 0;
DefensiveSpell.Nullify(target);
}
if (reflect)
{
target.FixedEffect(0x37B9, 10, 5);
var temp = caster;
caster = target;
target = temp;
}
}
else if (target is BaseCreature creature)
if (target is BaseCreature creature)
{
var reflect = false;
creature.CheckReflect(caster, ref reflect);
if (reflect)
{
creature.FixedEffect(0x37B9, 10, 5);
var temp = caster;
caster = creature;
target = temp;
}
}
if (reflect)
{
target.FixedEffect(0x37B9, 10, 5);
(caster, target) = (target, caster);
}
return reflect;
}
public static void Damage(Spell spell, Mobile target, double damage)
@ -995,20 +944,21 @@ namespace Server.Spells
{
(from as BaseCreature)?.AlterSpellDamageTo(target, ref iDamage);
(target as BaseCreature)?.AlterSpellDamageFrom(from, ref iDamage);
var bcTarget = target as BaseCreature;
bcTarget?.AlterSpellDamageFrom(from, ref iDamage);
target.Damage(iDamage, from);
if (from != null)
{
bcTarget?.OnHarmfulSpell(from);
bcTarget?.OnDamagedBySpell(from);
}
}
else
{
new SpellDamageTimer(spell, target, from, iDamage, delay).Start();
}
if (target is BaseCreature c && from != null && delay == TimeSpan.Zero)
{
c.OnHarmfulSpell(from);
c.OnDamagedBySpell(from);
}
}
public static void Damage(
@ -1086,6 +1036,11 @@ namespace Server.Spells
public static void DoLeech(int damageGiven, Mobile from, Mobile target)
{
if (target == null)
{
return;
}
var context = TransformationSpellHelper.GetContext(from);
if (context == null) /* cleanup */
@ -1134,7 +1089,7 @@ namespace Server.Spells
m_Damage = damage;
m_Spell = s;
if (m_Spell?.DelayedDamage == true && !m_Spell.DelayedDamageStacking)
if (m_Spell?.DelayedDamage == true)
{
m_Spell.StartDelayedDamageContext(target, this);
}
@ -1143,7 +1098,6 @@ namespace Server.Spells
protected override void OnTick()
{
(m_From as BaseCreature)?.AlterSpellDamageTo(m_Target, ref m_Damage);
(m_Target as BaseCreature)?.AlterSpellDamageFrom(m_From, ref m_Damage);
m_Target.Damage(m_Damage);
@ -1181,7 +1135,8 @@ namespace Server.Spells
m_Chaos = chaos;
m_DFA = dfa;
m_Spell = s;
if (m_Spell?.DelayedDamage == true && !m_Spell.DelayedDamageStacking)
if (m_Spell?.DelayedDamage == true)
{
m_Spell.StartDelayedDamageContext(target, this);
}
@ -1189,10 +1144,9 @@ namespace Server.Spells
protected override void OnTick()
{
var bcFrom = m_From as BaseCreature;
var bcTarg = m_Target as BaseCreature;
if (bcFrom != null && m_Target != null)
if (m_From is BaseCreature bcFrom && m_Target != null)
{
bcFrom.AlterSpellDamageTo(m_Target, ref m_Damage);
}
@ -1253,7 +1207,7 @@ namespace Server.Spells
public static bool OnCast(Mobile caster, Spell spell)
{
if (!(spell is ITransformationSpell transformSpell))
if (spell is not ITransformationSpell transformSpell)
{
return false;
}

View file

@ -51,9 +51,8 @@ namespace Server.Spells.Eighth
}
else if (m.Region?.IsPartOf("Khaldun") == true)
{
Caster.SendLocalizedMessage(
1010395
); // The veil of death in this area is too strong and resists thy efforts to restore life.
// The veil of death in this area is too strong and resists thy efforts to restore life.
Caster.SendLocalizedMessage(1010395);
}
else if (CheckBSequence(m, true))
{

View file

@ -27,6 +27,8 @@ namespace Server.Spells.First
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
// TODO: StoneForm immunity
SpellHelper.AddStatCurse(Caster, m, StatType.Int);
m.Spell?.OnCasterHurt();

View file

@ -1,3 +1,4 @@
using System;
using Server.Targeting;
namespace Server.Spells.First
@ -18,7 +19,7 @@ namespace Server.Spells.First
public override SpellCircle Circle => SpellCircle.First;
public override bool DelayedDamageStacking => !Core.AOS;
public override Type[] DelayedDamageSpellFamilyStacking => AOSNoDelayedDamageStackingSelf;
public override bool DelayedDamage => true;
@ -30,6 +31,12 @@ namespace Server.Spells.First
SpellHelper.Turn(source, m);
if (Core.SA && HasDelayedDamageContext(m))
{
DoHurtFizzle();
return;
}
SpellHelper.CheckReflect((int)Circle, ref source, ref m);
double damage;

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Collections;
using Server.Mobiles;
namespace Server.Spells.Fourth
@ -34,37 +33,39 @@ namespace Server.Spells.Fourth
SpellHelper.GetSurfaceTop(ref p);
var targets = new List<Mobile>();
var map = Caster.Map;
var directTarget = p as Mobile;
var loc = new Point3D(p);
if (map != null)
{
using var pool = PooledRefQueue<Mobile>.Create();
var directTarget = p as Mobile;
var loc = new Point3D(p);
var feluccaRules = map.Rules == MapRules.FeluccaRules;
// You can target any living mobile directly, beneficial checks apply
if (directTarget != null && Caster.CanBeBeneficial(directTarget, false))
{
targets.Add(directTarget);
pool.Enqueue(directTarget);
}
var eable = map.GetMobilesInRange(loc, 2);
targets.AddRange(eable.Where(m => m != directTarget).Where(m => AreaCanTarget(m, feluccaRules)));
foreach (var m in eable)
{
if (m != directTarget && AreaCanTarget(m, feluccaRules))
{
pool.Enqueue(m);
}
}
eable.Free();
}
Effects.PlaySound(loc, Caster.Map, 0x299);
Effects.PlaySound(loc, Caster.Map, 0x299);
if (targets.Count > 0)
{
var cured = 0;
for (var i = 0; i < targets.Count; ++i)
while (pool.Count > 0)
{
var m = targets[i];
var m = pool.Dequeue();
Caster.DoBeneficial(m);

View file

@ -19,7 +19,7 @@ namespace Server.Spells.Fourth
Reagent.SulfurousAsh
);
private static readonly Dictionary<Mobile, int> _Table = new();
private static readonly Dictionary<Mobile, int> _table = new();
public ArchProtectionSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
@ -107,12 +107,12 @@ namespace Server.Spells.Fourth
private static void AddEntry(Mobile m, int v)
{
_Table[m] = v;
_table[m] = v;
}
public static void RemoveEntry(Mobile m)
{
if (_Table.Remove(m, out var v))
if (_table.Remove(m, out var v))
{
m.EndAction<ArchProtectionSpell>();
m.VirtualArmorMod -= Math.Min(v, m.VirtualArmorMod);

View file

@ -15,7 +15,7 @@ namespace Server.Spells.Fourth
Reagent.SulfurousAsh
);
private static readonly HashSet<Mobile> m_UnderEffect = new();
private static readonly HashSet<Mobile> _underEffect = new();
public CurseSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
@ -41,7 +41,7 @@ namespace Server.Spells.Fourth
if (Caster.Player && m.Player /*&& Caster != m */ && !UnderEffect(m))
{
var duration = SpellHelper.GetDuration(Caster, m);
m_UnderEffect.Add(m);
_underEffect.Add(m);
Timer.StartTimer(duration, () => RemoveEffect(m));
m.UpdateResistances();
}
@ -71,13 +71,13 @@ namespace Server.Spells.Fourth
Caster.Target = new SpellTargetMobile(this, TargetFlags.Harmful, Core.ML ? 10 : 12);
}
public static void RemoveEffect(Mobile m)
public static bool RemoveEffect(Mobile m)
{
m_UnderEffect.Remove(m);
var effectRemoved = _underEffect.Remove(m);
m.UpdateResistances();
return effectRemoved;
}
public static bool UnderEffect(Mobile m) => m_UnderEffect.Contains(m);
public static bool UnderEffect(Mobile m) => _underEffect.Contains(m);
}
}

View file

@ -55,7 +55,7 @@ namespace Server.Spells.Second
public override void OnCast()
{
Caster.Target = new SpellTargetItem(this, range: Core.ML ? 10 : 12);
Caster.SendMessage("What do you wish to untrap?"); // TODO: Localization?
Caster.SendLocalizedMessage(502368);
}
}
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Server.Collections;
namespace Server.Spells.Seventh
{
@ -27,89 +26,77 @@ namespace Server.Spells.Seventh
public void Target(IPoint3D p)
{
if (SpellHelper.CheckTown(p, Caster) && CheckSequence())
var loc = (p as Item)?.GetWorldLocation() ?? new Point3D(p);
if (SpellHelper.CheckTown(loc, Caster) && CheckSequence())
{
SpellHelper.Turn(Caster, p);
if (p is Item item)
{
p = item.GetWorldLocation();
}
var targets = new List<Mobile>();
var map = Caster.Map;
var playerVsPlayer = false;
if (map != null)
{
var eable = map.GetMobilesInRange(new Point3D(p), 2);
using var pool = PooledRefQueue<Mobile>.Create();
var pvp = false;
targets.AddRange(
eable.Where(
m =>
{
if (Core.AOS && (m == Caster || !Caster.InLOS(m)) ||
!SpellHelper.ValidIndirectTarget(Caster, m) ||
!Caster.CanBeHarmful(m, false))
{
return false;
}
if (m.Player)
{
playerVsPlayer = true;
}
return true;
}
)
.ToList()
);
eable.Free();
}
double damage;
damage = Core.AOS
? GetNewAosDamage(51, 1, 5, playerVsPlayer)
: Utility.Random(27, 22);
if (targets.Count > 0)
{
if (Core.AOS && targets.Count > 2)
var eable = map.GetMobilesInRange(loc, 2);
foreach (var m in eable)
{
damage = damage * 2 / targets.Count;
}
else if (!Core.AOS)
{
damage /= targets.Count;
}
for (var i = 0; i < targets.Count; ++i)
{
var toDeal = damage;
var m = targets[i];
if (!Core.AOS && CheckResisted(m))
if (Core.AOS && (m == Caster || !Caster.InLOS(m)) ||
!SpellHelper.ValidIndirectTarget(Caster, m) ||
!Caster.CanBeHarmful(m, false))
{
toDeal *= 0.5;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
continue;
}
toDeal *= GetDamageScalar(m);
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, toDeal, 0, 0, 0, 0, 100);
if (m.Player)
{
pvp = true;
}
m.BoltEffect(0);
pool.Enqueue(m);
}
eable.Free();
if (pool.Count > 0)
{
double damage = Core.AOS
? GetNewAosDamage(51, 1, 5, pvp)
: Utility.Random(27, 22);
if (pool.Count > 2)
{
if (Core.AOS)
{
damage *= 2;
}
damage /= pool.Count;
}
while (pool.Count > 0)
{
var toDeal = damage;
var m = pool.Dequeue();
if (!Core.AOS && CheckResisted(m))
{
toDeal *= 0.5;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
toDeal *= GetDamageScalar(m);
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, toDeal, 0, 0, 0, 0, 100);
m.BoltEffect(0);
}
}
else
{
Caster.PlaySound(0x29);
}
}
else
{
Caster.PlaySound(0x29);
}
}

View file

@ -45,10 +45,8 @@ namespace Server.Spells.Seventh
}
else
{
duration = TimeSpan.FromSeconds(
Caster.Skills.Magery.Value * 0.28 +
2.0
); // (28% of magery) + 2.0 seconds
// (28% of magery) + 2.0 seconds
duration = TimeSpan.FromSeconds(Caster.Skills.Magery.Value * 0.28 + 2.0);
}
var itemID = eastToWest ? 0x3946 : 0x3956;

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Server.Collections;
namespace Server.Spells.Seventh
{
@ -36,8 +35,6 @@ namespace Server.Spells.Seventh
p = item.GetWorldLocation();
}
List<Mobile> targets;
var map = Caster.Map;
var playerVsPlayer = false;
@ -46,69 +43,63 @@ namespace Server.Spells.Seventh
if (map != null)
{
var eable = map.GetMobilesInRange(loc, 2);
targets = eable.Where(
m =>
{
if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) ||
!Caster.CanBeHarmful(m, false) ||
Core.AOS && !Caster.InLOS(m))
{
return false;
}
if (m.Player)
{
playerVsPlayer = true;
}
return true;
}
)
.ToList();
eable.Free();
}
else
{
targets = new List<Mobile>();
}
double damage = Core.AOS
? GetNewAosDamage(51, 1, 5, playerVsPlayer)
: Utility.Random(27, 22);
if (targets.Count > 0)
{
Effects.PlaySound(loc, Caster.Map, 0x160);
if (Core.AOS && targets.Count > 2)
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
{
damage = damage * 2 / targets.Count;
}
else if (!Core.AOS)
{
damage /= targets.Count;
}
for (var i = 0; i < targets.Count; ++i)
{
var m = targets[i];
var toDeal = damage;
if (!Core.AOS && CheckResisted(m))
if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) ||
!Caster.CanBeHarmful(m, false) || Core.AOS && !Caster.InLOS(m))
{
damage *= 0.5;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
continue;
}
toDeal *= GetDamageScalar(m);
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, toDeal, 0, 100, 0, 0, 0);
if (m.Player)
{
playerVsPlayer = true;
}
Caster.MovingParticles(m, 0x36D4, 7, 0, false, true, 9501, 1, 0, 0x100);
queue.Enqueue(m);
}
eable.Free();
double damage = Core.AOS
? GetNewAosDamage(51, 1, 5, playerVsPlayer)
: Utility.Random(27, 22);
int count = queue.Count;
if (count > 0)
{
Effects.PlaySound(loc, Caster.Map, 0x160);
if (Core.AOS && count > 2)
{
damage = damage * 2 / count;
}
else if (!Core.AOS)
{
damage /= count;
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
var toDeal = damage;
if (!Core.AOS && CheckResisted(m))
{
damage *= 0.5;
m.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
toDeal *= GetDamageScalar(m);
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, toDeal, 0, 100, 0, 0, 0);
Caster.MovingParticles(m, 0x36D4, 7, 0, false, true, 9501, 1, 0, 0x100);
}
}
}
}

View file

@ -20,22 +20,26 @@ namespace Server.Spells.Sixth
public override SpellCircle Circle => SpellCircle.Sixth;
public override bool DelayedDamageStacking => !Core.AOS;
public override Type[] DelayedDamageSpellFamilyStacking => AOSNoDelayedDamageStackingSelf;
public override bool DelayedDamage => false;
public void Target(Mobile m)
{
if (Core.SA && HasDelayedDamageContext(m))
{
DoHurtFizzle();
return;
}
if (Caster.CanBeHarmful(m) && CheckSequence())
{
Mobile attacker = Caster, defender = m;
Mobile defender = m;
SpellHelper.Turn(Caster, m);
SpellHelper.CheckReflect((int)Circle, Caster, ref m);
var t = new InternalTimer(this, attacker, defender, m);
t.Start();
var t = new InternalTimer(this, Caster, defender, m).Start();
}
FinishSequence();
@ -48,52 +52,52 @@ namespace Server.Spells.Sixth
private class InternalTimer : Timer
{
private readonly Mobile m_Attacker;
private readonly Mobile m_Defender;
private readonly MagerySpell m_Spell;
private readonly Mobile m_Target;
private readonly Mobile _attacker;
private readonly Mobile _defender;
private readonly MagerySpell _spell;
private readonly Mobile _target;
public InternalTimer(MagerySpell spell, Mobile attacker, Mobile defender, Mobile target)
: base(TimeSpan.FromSeconds(Core.AOS ? 3.0 : 2.5))
{
m_Spell = spell;
m_Attacker = attacker;
m_Defender = defender;
m_Target = target;
_spell = spell;
_attacker = attacker;
_defender = defender;
_target = target;
m_Spell?.StartDelayedDamageContext(attacker, this);
_spell?.StartDelayedDamageContext(_attacker, this);
}
protected override void OnTick()
{
if (m_Attacker.HarmfulCheck(m_Defender))
if (_attacker.HarmfulCheck(_defender))
{
double damage;
if (Core.AOS)
{
damage = m_Spell.GetNewAosDamage(40, 1, 5, m_Defender);
damage = _spell.GetNewAosDamage(40, 1, 5, _defender);
}
else
{
damage = Utility.Random(23, 22);
if (m_Spell.CheckResisted(m_Target))
if (_spell.CheckResisted(_target))
{
damage *= 0.75;
m_Target.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
_target.SendLocalizedMessage(501783); // You feel yourself resisting magical energy.
}
damage *= m_Spell.GetDamageScalar(m_Target);
damage *= _spell.GetDamageScalar(_target);
}
m_Target.FixedParticles(0x36BD, 20, 10, 5044, EffectLayer.Head);
m_Target.PlaySound(0x307);
_target.FixedParticles(0x36BD, 20, 10, 5044, EffectLayer.Head);
_target.PlaySound(0x307);
SpellHelper.Damage(m_Spell, m_Target, damage, 0, 100, 0, 0, 0);
SpellHelper.Damage(_spell, _target, damage, 0, 100, 0, 0, 0);
m_Spell?.RemoveDelayedDamageContext(m_Attacker);
_spell?.RemoveDelayedDamageContext(_attacker);
}
}
}

View file

@ -43,24 +43,13 @@ namespace Server.Spells.Third
{
if (Caster.InRange(m, 2))
{
var total = (Caster.Skills.Magery.Fixed + Caster.Skills.Poisoning.Fixed) / 2;
if (total >= 1000)
level = (Caster.Skills.Magery.Fixed + Caster.Skills.Poisoning.Fixed) / 2 switch
{
level = 3;
}
else if (total > 850)
{
level = 2;
}
else if (total > 650)
{
level = 1;
}
else
{
level = 0;
}
>= 1000 => 3,
> 850 => 2,
> 650 => 1,
_ => 0
};
}
else
{