fix: Changes Map.Sector.Mobiles to link list & Fixes various related crash bugs (#1553)

### Summary

Eliminates `IPooledEnumerable<T>` and `eable.Free()` from `Map` for mobiles. This drastically simplifies code that iterates in range, for example:

```cs
foreach (var m in m.GetMobilesInRange(5))
{
}
```
The code above no longer requires an eable and calling `Free()`.

- [X] Fixed several locations where an NPC that was damaged would cause a server crash.
- [X] Removed an unnecessary allocation in guard fake calls (NPCs calling guards on you)
- [X] Fixes damage precision loss in Poison Strike Spell
- [X] BogThing no longer attempts to "search" for boglings to eat when it is at full health
This commit is contained in:
Kamron Batman 2023-10-29 22:42:46 -07:00 • committed by GitHub
parent 27f0cec1fa
commit 28c06c1cc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 796 additions and 591 deletions

View file

@ -21,9 +21,8 @@ namespace Server.Spells.Bushido
var weapon = attacker.Weapon;
var eable = attacker.GetMobilesInRange(weapon.MaxRange);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
foreach (var m in attacker.GetMobilesInRange(weapon.MaxRange))
{
if (m != defender && m.Combatant == attacker)
{

View file

@ -46,9 +46,8 @@ namespace Server.Spells.Chivalry
var chiv = Caster.Skills.Chivalry.Value;
var eable = Caster.GetMobilesInRange(8);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
foreach (var m in Caster.GetMobilesInRange(8))
{
if (Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false))
{

View file

@ -1,4 +1,5 @@
using System;
using Server.Collections;
using Server.Items;
namespace Server.Spells.Chivalry
@ -54,6 +55,7 @@ namespace Server.Spells.Chivalry
0
);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in Caster.GetMobilesInRange(3))
{
if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanBeHarmful(m, false) ||
@ -62,6 +64,13 @@ namespace Server.Spells.Chivalry
continue;
}
queue.Enqueue(m);
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
var damage = Math.Clamp(ComputePowerValue(10) + Utility.RandomMinMax(0, 2), 8, 24);
Caster.DoHarmful(m);

View file

@ -34,10 +34,9 @@ namespace Server.Spells.Chivalry
{
if (CheckSequence())
{
var eable = Caster.GetMobilesInRange(3);
using var pool = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
foreach (var m in Caster.GetMobilesInRange(3))
{
if (m is not BaseCreature { IsAnimatedDead: true } && Caster != m && m.InLOS(Caster) &&
Caster.CanBeBeneficial(m, false, true) && m is not Golem)

View file

@ -37,9 +37,8 @@ namespace Server.Spells.Eighth
return;
}
var eable = Caster.GetMobilesInRange(1 + (int)(Caster.Skills.Magery.Value / 15.0));
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
foreach (var m in Caster.GetMobilesInRange(1 + (int)(Caster.Skills.Magery.Value / 15.0)))
{
if (Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) &&
(!Core.AOS || Caster.InLOS(m)))

View file

@ -243,17 +243,15 @@ namespace Server.Spells.Fifth
if (map != null && caster != null)
{
var eastToWest = m_Item.ItemID == 0x3915;
var eable = map.GetMobilesInBounds(
new Rectangle2D(
m_Item.X - (eastToWest ? 0 : 1),
m_Item.Y - (eastToWest ? 1 : 0),
eastToWest ? 1 : 2,
eastToWest ? 2 : 1
)
var bounds = new Rectangle2D(
m_Item.X - (eastToWest ? 0 : 1),
m_Item.Y - (eastToWest ? 1 : 0),
eastToWest ? 1 : 2,
eastToWest ? 2 : 1
);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
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))

View file

@ -48,8 +48,7 @@ namespace Server.Spells.Fourth
pool.Enqueue(directTarget);
}
var eable = map.GetMobilesInRange(loc, 2);
foreach (var m in eable)
foreach (var m in map.GetMobilesInRange(loc, 2))
{
if (m != directTarget && AreaCanTarget(m, feluccaRules))
{

View file

@ -48,10 +48,8 @@ namespace Server.Spells.Fourth
return;
}
var eable = Caster.Map.GetMobilesInRange(loc, Core.AOS ? 2 : 3);
using var targets = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
foreach (var m in Caster.Map.GetMobilesInRange(loc, Core.AOS ? 2 : 3))
{
if (Caster.CanBeBeneficial(m, false))
{

View file

@ -244,7 +244,7 @@ namespace Server.Spells.Fourth
}
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in m_Item.GetMobilesInRange(0))
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))
@ -256,10 +256,6 @@ namespace Server.Spells.Fourth
while (queue.Count > 0)
{
var m = queue.Dequeue();
if (m == null)
{
continue;
}
if (SpellHelper.CanRevealCaster(m))
{

View file

@ -41,6 +41,10 @@ namespace Server.Spells.Mysticism
Caster.PlaySound(0x64C);
var primarySkill = GetBaseSkill(Caster);
var secondarySkill = GetDamageSkill(Caster);
var cureChance = 10000 + (int)((primarySkill + secondarySkill) / 2 * 75);
using var pool = PooledRefQueue<Mobile>.Create();
pool.Enqueue(m);
@ -49,12 +53,7 @@ namespace Server.Spells.Mysticism
{
foreach (Mobile mob in Caster.Map.GetMobilesInRange(m.Location, 2))
{
if (mob == m)
{
continue;
}
if (casterParty.Contains(mob) && Caster.CanBeBeneficial(mob, false))
if (mob != m && casterParty.Contains(mob) && Caster.CanBeBeneficial(mob, false))
{
pool.Enqueue(mob);
if (pool.Count == 4)
@ -65,9 +64,6 @@ namespace Server.Spells.Mysticism
}
}
var primarySkill = GetBaseSkill(Caster);
var secondarySkill = GetDamageSkill(Caster);
var toHeal = ((int)((primarySkill + secondarySkill) / 4.0) + Utility.RandomMinMax(-3, 3)) / pool.Count;
while (pool.Count > 0)
@ -102,9 +98,9 @@ namespace Server.Spells.Mysticism
if (target.Poisoned)
{
var poisonLevel = target.Poison.Level + 1;
var chanceToCure = 10000 + (int)((primarySkill + secondarySkill) / 2 * 75) - poisonLevel * 1750;
var chanceToCure = cureChance - poisonLevel * 1750;
if (chanceToCure > Utility.Random(10000) && target.CurePoison(Caster))
if (chanceToCure > 10000 || chanceToCure > Utility.Random(10000) && target.CurePoison(Caster))
{
toHealMod -= (int)(toHeal * poisonLevel * 0.15);
}

View file

@ -1,4 +1,5 @@
using System;
using Server.Collections;
using Server.Engines.CannedEvil;
using Server.Engines.PartySystem;
using Server.Factions;
@ -94,14 +95,23 @@ namespace Server.Spells.Necromancy
if (map != null)
{
// Surprisingly, no sparkle type effects
// Cannot move a mobile while iterating mobiles in range, so use a queue
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in r.Spawn.GetMobilesInRange(Range))
{
if (IsValidTarget(m))
{
m.Location = GetNearestShrine(m);
queue.Enqueue(m);
}
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
// Surprisingly, no sparkle type effects
m.Location = GetNearestShrine(m);
}
}
}

View file

@ -80,8 +80,7 @@ namespace Server.Spells.Necromancy
var cbc = Caster as BaseCreature;
var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned);
var eable = m.GetMobilesInRange(2);
foreach (Mobile targ in eable)
foreach (Mobile targ in m.GetMobilesInRange(2))
{
if (targ == Caster || m == targ || !SpellHelper.ValidIndirectTarget(Caster, targ)
|| !Caster.CanBeHarmful(targ, false))

View file

@ -51,8 +51,7 @@ namespace Server.Spells.Necromancy
var cbc = Caster as BaseCreature;
var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned);
var eable = Caster.GetMobilesInRange(Core.ML ? 4 : 5);
foreach (var targ in eable)
foreach (var targ in Caster.GetMobilesInRange(Core.ML ? 4 : 5))
{
if (targ == Caster
|| !Caster.InLOS(targ)
@ -106,7 +105,7 @@ namespace Server.Spells.Necromancy
double damage = Utility.RandomMinMax(30, 35);
damage *= 300 + m.Karma / 100 + GetDamageSkill(Caster) * 10;
damage *= 300 + m.Karma / 100.0 + GetDamageSkill(Caster) * 10;
damage /= 1000;
var sdiBonus = AosAttributes.GetValue(Caster, AosAttribute.SpellDamage);

View file

@ -35,11 +35,9 @@ namespace Server.Spells.Seventh
if (map != null)
{
using var pool = PooledRefQueue<Mobile>.Create();
var pvp = false;
var eable = map.GetMobilesInRange(loc, 2);
foreach (var m in eable)
using var pool = PooledRefQueue<Mobile>.Create();
foreach (var m in map.GetMobilesInRange(loc, 2))
{
if (Core.AOS && (m == Caster || !Caster.InLOS(m)) ||
!SpellHelper.ValidIndirectTarget(Caster, m) ||

View file

@ -35,11 +35,8 @@ namespace Server.Spells.Seventh
if (map != null)
{
var eable = map.GetMobilesInRange<BaseCreature>(new Point3D(p), 8);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var bc in eable)
foreach (var bc in map.GetMobilesInRange<BaseCreature>(new Point3D(p), 8))
{
if (!(bc.IsDispellable && Caster.CanBeHarmful(bc, false)))
{

View file

@ -42,9 +42,8 @@ namespace Server.Spells.Seventh
if (map != null)
{
var eable = map.GetMobilesInRange(loc, 2);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
foreach (var m in map.GetMobilesInRange(loc, 2))
{
if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) ||
!Caster.CanBeHarmful(m, false) || Core.AOS && !Caster.InLOS(m))

View file

@ -32,9 +32,7 @@ namespace Server.Spells.Sixth
if (map != null)
{
var eable = map.GetMobilesInRange(new Point3D(p), 2);
foreach (var m in eable)
foreach (var m in map.GetMobilesInRange(new Point3D(p), 2))
{
if (Core.AOS && (m == Caster || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanSee(m) ||
!Caster.CanBeHarmful(m, false)))

View file

@ -29,12 +29,8 @@ namespace Server.Spells.Sixth
if (map != null)
{
var eable = map.GetMobilesInRange(
new Point3D(p),
1 + (int)(Caster.Skills.Magery.Value / 20.0)
);
foreach (var m in eable)
var range = 1 + (int)(Caster.Skills.Magery.Value / 20.0);
foreach (var m in map.GetMobilesInRange(new Point3D(p), range))
{
if (m is ShadowKnight && (m.X != p.X || m.Y != p.Y))
{

View file

@ -114,8 +114,7 @@ namespace Server.Spells.Spellweaving
private bool CheckArcanists()
{
var spellWeaving = Caster.Skills.Spellweaving.Value;
var eable = Caster.GetMobilesInRange(1);
foreach (var m in eable)
foreach (var m in Caster.GetMobilesInRange(1))
{
if (m != Caster && m is PlayerMobile && Caster.CanBeBeneficial(m, false) &&
Math.Abs(spellWeaving - m.Skills.Spellweaving.Value) <= 20)
@ -132,9 +131,7 @@ namespace Server.Spells.Spellweaving
// Everyone gets the Arcane Focus, power capped elsewhere
var pool = PooledRefQueue<Mobile>.Create();
var eable = Caster.GetMobilesInRange(1);
foreach (var m in eable)
foreach (var m in Caster.GetMobilesInRange(1))
{
if (m != Caster && m is PlayerMobile && Caster.CanBeBeneficial(m, false) &&
Math.Abs(spellWeaving - m.Skills.Spellweaving.Value) <= 20)

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Server.Collections;
namespace Server.Spells.Spellweaving
{
@ -24,7 +25,6 @@ namespace Server.Spells.Spellweaving
{
Caster.PlaySound(0x5C6);
var range = 5 + FocusLevel;
var damage = 25 + FocusLevel;
var skill = Caster.Skills.Spellweaving.Value;
@ -34,9 +34,8 @@ namespace Server.Spells.Spellweaving
var fcMalus = FocusLevel + 1;
var ssiMalus = 2 * (FocusLevel + 1);
var eable = Caster.GetMobilesInRange(range);
foreach (var m in eable)
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in Caster.GetMobilesInRange(5 + FocusLevel))
{
if (Caster == m || !Caster.InLOS(m) || !SpellHelper.ValidIndirectTarget(Caster, m) ||
!Caster.CanBeHarmful(m, false))
@ -44,6 +43,12 @@ namespace Server.Spells.Spellweaving
continue;
}
queue.Enqueue(m);
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0);

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Server.Collections;
namespace Server.Spells.Spellweaving
{
@ -38,12 +39,10 @@ namespace Server.Spells.Spellweaving
var pvpDamage = damage * (100 + Math.Min(sdiBonus, 15)) / 100;
var range = 2 + FocusLevel;
var duration = TimeSpan.FromSeconds(5 + FocusLevel);
var eable = Caster.GetMobilesInRange(range);
foreach (var m in eable)
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in Caster.GetMobilesInRange(2 + FocusLevel))
{
if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanBeHarmful(m, false) ||
!Caster.InLOS(m))
@ -51,6 +50,12 @@ namespace Server.Spells.Spellweaving
continue;
}
queue.Enqueue(m);
}
while (queue.Count > 0)
{
var m = queue.Dequeue();
Caster.DoHarmful(m);
var oldSpell = m.Spell as Spell;