feat: Adds proper OSI tracking up to 120 tiles. (#2281)
### Summary Fixes tracking skill to 10 tiles per 10% up to 120 tiles.
This commit is contained in:
parent
ad26ab6260
commit
5fb0e806c5
7 changed files with 417 additions and 66 deletions
|
|
@ -215,8 +215,8 @@ public class ItemByDistanceEnumeratorTests
|
|||
var center = new Point3D(700, 700, 0);
|
||||
const int range = 5;
|
||||
|
||||
var testItem2 = new TestItem2((Serial)Utility.RandomMinMax(0x100u, 0xFFFu));
|
||||
var testItem = new TestItem((Serial)Utility.RandomMinMax(0x100u, 0xFFFu));
|
||||
var testItem2 = new TestItem2(World.NewItem);
|
||||
var testItem = new TestItem(World.NewItem);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -438,7 +438,7 @@ public class ItemByDistanceEnumeratorTests
|
|||
|
||||
private static TestItem CreateItem(Map map, Point3D location)
|
||||
{
|
||||
var Item = new TestItem((Serial)Utility.RandomMinMax(0x100u, 0xFFFu));
|
||||
var Item = new TestItem(World.NewItem);
|
||||
Item.MoveToWorld(location, map);
|
||||
return Item;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,8 +215,8 @@ public class MobileByDistanceEnumeratorTests
|
|||
var center = new Point3D(700, 700, 0);
|
||||
const int range = 5;
|
||||
|
||||
var player = new TestPlayerMobile((Serial)Utility.RandomMinMax(0x100u, 0xFFFu));
|
||||
var npc = new TestMobile((Serial)Utility.RandomMinMax(0x100u, 0xFFFu));
|
||||
var player = new TestPlayerMobile(World.NewMobile);
|
||||
var npc = new TestMobile(World.NewMobile);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -440,7 +440,7 @@ public class MobileByDistanceEnumeratorTests
|
|||
|
||||
private static TestMobile CreateMobile(Map map, Point3D location)
|
||||
{
|
||||
var mobile = new TestMobile((Serial)Utility.RandomMinMax(0x100u, 0xFFFu));
|
||||
var mobile = new TestMobile(World.NewMobile);
|
||||
mobile.DefaultMobileInit();
|
||||
mobile.MoveToWorld(location, map);
|
||||
return mobile;
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ public class MobileEnumeratorTests
|
|||
|
||||
private static Mobile CreateMobile(Map map, Point3D location)
|
||||
{
|
||||
var mobile = new Mobile((Serial)Utility.RandomMinMax(0x100u, 0xFFFu));
|
||||
var mobile = new Mobile(World.NewMobile);
|
||||
mobile.DefaultMobileInit();
|
||||
mobile.MoveToWorld(location, map);
|
||||
return mobile;
|
||||
|
|
|
|||
63
Projects/Server/Mobiles/Mobile.Enumerators.cs
Normal file
63
Projects/Server/Mobiles/Mobile.Enumerators.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Server;
|
||||
|
||||
public partial class Mobile
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemAtEnumerable<Item> GetItemsAt() =>
|
||||
m_Map == null ? Map.ItemAtEnumerable<Item>.Empty : m_Map.GetItemsAt(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemAtEnumerable<T> GetItemsAt<T>() where T : Item =>
|
||||
m_Map == null ? Map.ItemAtEnumerable<T>.Empty : m_Map.GetItemsAt<T>(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemBoundsEnumerable<Item> GetItemsInRange(int range) => GetItemsInRange<Item>(range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemBoundsEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
|
||||
m_Map == null ? Map.ItemBoundsEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Location, range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileAtEnumerable<Mobile> GetMobilesInRange() => GetMobilesInRange<Mobile>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileAtEnumerable<T> GetMobilesInRange<T>() where T : Mobile =>
|
||||
m_Map == null ? Map.MobileAtEnumerable<T>.Empty : m_Map.GetMobilesAt<T>(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileBoundsEnumerable<Mobile> GetMobilesInRange(int range) => GetMobilesInRange<Mobile>(range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileBoundsEnumerable<T> GetMobilesInRange<T>(int range) where T : Mobile =>
|
||||
m_Map == null ? Map.MobileBoundsEnumerable<T>.Empty : m_Map.GetMobilesInRange<T>(m_Location, range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ClientAtEnumerable GetClientsAt() =>
|
||||
m_Map == null ? Map.ClientAtEnumerable.Empty : Map.GetClientsAt(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ClientBoundsEnumerable GetClientsInRange(int range) =>
|
||||
m_Map == null ? Map.ClientBoundsEnumerable.Empty : Map.GetClientsInRange(m_Location, range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemDistanceEnumerable<Item> GetItemsInRangeByDistance(int range) =>
|
||||
GetItemsInRangeByDistance<Item>(range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemDistanceEnumerable<T> GetItemsInRangeByDistance<T>(int range) where T : Item =>
|
||||
m_Map == null ? default : m_Map.GetItemsInRangeByDistance<T>(m_Location, range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileDistanceEnumerable<Mobile> GetMobilesInRangeByDistance(int range) =>
|
||||
GetMobilesInRangeByDistance<Mobile>(range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileDistanceEnumerable<T> GetMobilesInRangeByDistance<T>(int range) where T : Mobile =>
|
||||
m_Map == null ? default : m_Map.GetMobilesInRangeByDistance<T>(m_Location, range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ClientDistanceEnumerable GetClientsInRangeByDistance(int range) =>
|
||||
m_Map == null ? default : m_Map.GetClientsInRangeByDistance(m_Location, range);
|
||||
}
|
||||
|
|
@ -8032,43 +8032,6 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
return -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemAtEnumerable<Item> GetItemsAt() =>
|
||||
m_Map == null ? Map.ItemAtEnumerable<Item>.Empty : m_Map.GetItemsAt(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemAtEnumerable<T> GetItemsAt<T>() where T : Item =>
|
||||
m_Map == null ? Map.ItemAtEnumerable<T>.Empty : m_Map.GetItemsAt<T>(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemBoundsEnumerable<Item> GetItemsInRange(int range) => GetItemsInRange<Item>(range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ItemBoundsEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
|
||||
m_Map == null ? Map.ItemBoundsEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Location, range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileAtEnumerable<Mobile> GetMobilesInRange() => GetMobilesInRange<Mobile>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileAtEnumerable<T> GetMobilesInRange<T>() where T : Mobile =>
|
||||
m_Map == null ? Map.MobileAtEnumerable<T>.Empty : m_Map.GetMobilesAt<T>(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileBoundsEnumerable<Mobile> GetMobilesInRange(int range) => GetMobilesInRange<Mobile>(range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.MobileBoundsEnumerable<T> GetMobilesInRange<T>(int range) where T : Mobile =>
|
||||
m_Map == null ? Map.MobileBoundsEnumerable<T>.Empty : m_Map.GetMobilesInRange<T>(m_Location, range);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ClientAtEnumerable GetClientsAt() =>
|
||||
m_Map == null ? Map.ClientAtEnumerable.Empty : Map.GetClientsAt(m_Location);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Map.ClientBoundsEnumerable GetClientsInRange(int range) =>
|
||||
m_Map == null ? Map.ClientBoundsEnumerable.Empty : Map.GetClientsInRange(m_Location, range);
|
||||
|
||||
public void SayTo(Mobile to, bool ascii, string text) =>
|
||||
PrivateOverheadMessage(MessageType.Regular, SpeechHue, ascii, text, to.NetState);
|
||||
|
||||
|
|
|
|||
299
Projects/UOContent.Tests/Tests/Skills/TrackingTests.cs
Normal file
299
Projects/UOContent.Tests/Tests/Skills/TrackingTests.cs
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Server;
|
||||
using Server.Mobiles;
|
||||
using Server.SkillHandlers;
|
||||
using Xunit;
|
||||
|
||||
namespace UOContent.Tests;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class TrackingTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that tracking correctly finds the closest mobiles when there are more than 12 available.
|
||||
/// This validates the GetClosestMobs logic, especially the early exit optimization.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Tracking_FindsClosestMobiles_WhenManyAvailable()
|
||||
{
|
||||
var map = Map.Felucca;
|
||||
var center = new Point3D(1000, 1000, 0);
|
||||
|
||||
var tracker = CreatePlayerMobile(map, center);
|
||||
tracker.Skills.Tracking.BaseFixedPoint = 1000; // 100.0 skill = 110 range
|
||||
|
||||
var mobiles = new List<TestAnimal>();
|
||||
|
||||
try
|
||||
{
|
||||
// Create 20 animals at various distances
|
||||
// First 12 should be the closest ones we find
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
var distance = i + 1; // Distance from 1 to 20
|
||||
var location = new Point3D(center.X + distance, center.Y, 0);
|
||||
var animal = CreateAnimal(map, location);
|
||||
mobiles.Add(animal);
|
||||
}
|
||||
|
||||
// Invoke tracking through the skill system
|
||||
// We can't directly test GetClosestMobs since it's private, but we can verify
|
||||
// the behavior by checking what the gump would show
|
||||
|
||||
// Use reflection to test the private method
|
||||
var method = typeof(TrackWhoGump).GetMethod(
|
||||
"GetClosestMobs",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
||||
);
|
||||
|
||||
Assert.NotNull(method);
|
||||
|
||||
var range = Math.Clamp(10 + (int)tracker.Skills.Tracking.Value, 0, 100);
|
||||
var result = (Mobile[])method.Invoke(null, new object[] { tracker, range, 0 }); // 0 = animals
|
||||
|
||||
// Should return at most 12 mobiles
|
||||
Assert.True(result.Length <= 12);
|
||||
|
||||
// Should return the 12 closest (distances 1-12)
|
||||
Assert.Equal(12, result.Length);
|
||||
|
||||
// Verify they are sorted by distance
|
||||
for (var i = 0; i < result.Length - 1; i++)
|
||||
{
|
||||
var dist1 = result[i].GetDistanceToSqrt(center);
|
||||
var dist2 = result[i + 1].GetDistanceToSqrt(center);
|
||||
Assert.True(dist1 <= dist2, $"Mobiles not sorted: {dist1} > {dist2}");
|
||||
}
|
||||
|
||||
// Verify the first mobile is the closest (distance 1)
|
||||
Assert.Equal(mobiles[0], result[0]);
|
||||
|
||||
// Verify the last mobile is at distance 12
|
||||
Assert.Equal(mobiles[11], result[11]);
|
||||
|
||||
// Verify mobile at distance 13 is NOT included
|
||||
Assert.DoesNotContain(mobiles[12], result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tracker?.Delete();
|
||||
foreach (var mob in mobiles)
|
||||
{
|
||||
mob?.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that tracking correctly handles the case where there are exactly 12 mobiles available.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Tracking_FindsAllMobiles_WhenExactly12Available()
|
||||
{
|
||||
var map = Map.Felucca;
|
||||
var center = new Point3D(2000, 2000, 0);
|
||||
|
||||
var tracker = CreatePlayerMobile(map, center);
|
||||
tracker.Skills.Tracking.BaseFixedPoint = 1000; // 100.0 skill = 110 range
|
||||
|
||||
var mobiles = new List<TestAnimal>();
|
||||
|
||||
try
|
||||
{
|
||||
// Create exactly 12 animals
|
||||
for (var i = 0; i < 12; i++)
|
||||
{
|
||||
var distance = i + 1;
|
||||
var location = new Point3D(center.X + distance, center.Y, 0);
|
||||
var animal = CreateAnimal(map, location);
|
||||
mobiles.Add(animal);
|
||||
}
|
||||
|
||||
var method = typeof(TrackWhoGump).GetMethod(
|
||||
"GetClosestMobs",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
||||
);
|
||||
|
||||
var range = Math.Clamp(10 + (int)tracker.Skills.Tracking.Value, 0, 100);
|
||||
var result = (Mobile[])method.Invoke(null, new object[] { tracker, range, 0 });
|
||||
|
||||
Assert.Equal(12, result.Length);
|
||||
|
||||
// Verify all mobiles are included
|
||||
foreach (var mob in mobiles)
|
||||
{
|
||||
Assert.Contains(mob, result);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
tracker?.Delete();
|
||||
foreach (var mob in mobiles)
|
||||
{
|
||||
mob?.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that tracking correctly handles the case where there are fewer than 12 mobiles available.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Tracking_FindsAllMobiles_WhenFewerThan12Available()
|
||||
{
|
||||
var map = Map.Felucca;
|
||||
var center = new Point3D(3000, 3000, 0);
|
||||
|
||||
var tracker = CreatePlayerMobile(map, center);
|
||||
tracker.Skills.Tracking.BaseFixedPoint = 1000;
|
||||
|
||||
var mobiles = new List<TestAnimal>();
|
||||
|
||||
try
|
||||
{
|
||||
// Create only 5 animals
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var distance = i + 1;
|
||||
var location = new Point3D(center.X + distance, center.Y, 0);
|
||||
var animal = CreateAnimal(map, location);
|
||||
mobiles.Add(animal);
|
||||
}
|
||||
|
||||
var method = typeof(TrackWhoGump).GetMethod(
|
||||
"GetClosestMobs",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
||||
);
|
||||
|
||||
var range = Math.Clamp(10 + (int)tracker.Skills.Tracking.Value, 0, 100);
|
||||
var result = (Mobile[])method.Invoke(null, new object[] { tracker, range, 0 });
|
||||
|
||||
Assert.Equal(5, result.Length);
|
||||
|
||||
// Verify all mobiles are included
|
||||
foreach (var mob in mobiles)
|
||||
{
|
||||
Assert.Contains(mob, result);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
tracker?.Delete();
|
||||
foreach (var mob in mobiles)
|
||||
{
|
||||
mob?.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the early exit optimization works correctly when mobiles in farther sectors
|
||||
/// are closer than mobiles in nearer sectors (worst case for the optimization).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Tracking_EarlyExitWorksCorrectly_WithFarSectorNearMobiles()
|
||||
{
|
||||
var map = Map.Felucca;
|
||||
// Use a location that puts mobiles in different sectors
|
||||
var center = new Point3D(1500, 1500, 0);
|
||||
|
||||
var tracker = CreatePlayerMobile(map, center);
|
||||
tracker.Skills.Tracking.BaseFixedPoint = 1000;
|
||||
|
||||
var mobiles = new List<TestAnimal>();
|
||||
|
||||
try
|
||||
{
|
||||
// Create 15 animals where some farther ones might be in closer proximity
|
||||
// but in different sectors
|
||||
for (var i = 0; i < 15; i++)
|
||||
{
|
||||
var distance = i + 1;
|
||||
// Alternate between X and Y to potentially cross sector boundaries
|
||||
var location = i % 2 == 0
|
||||
? new Point3D(center.X + distance, center.Y, 0)
|
||||
: new Point3D(center.X, center.Y + distance, 0);
|
||||
var animal = CreateAnimal(map, location);
|
||||
mobiles.Add(animal);
|
||||
}
|
||||
|
||||
var method = typeof(TrackWhoGump).GetMethod(
|
||||
"GetClosestMobs",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
||||
);
|
||||
|
||||
var range = Math.Clamp(10 + (int)tracker.Skills.Tracking.Value, 0, 100);
|
||||
var result = (Mobile[])method.Invoke(null, new object[] { tracker, range, 0 });
|
||||
|
||||
Assert.True(result.Length <= 12);
|
||||
|
||||
// Get the actual 12 closest by brute force
|
||||
var allDistances = mobiles
|
||||
.Select(m => (Mobile: m, Distance: m.GetDistanceToSqrt(center)))
|
||||
.OrderBy(x => x.Distance)
|
||||
.ToList();
|
||||
|
||||
var expected12Closest = allDistances.Take(12).ToList();
|
||||
|
||||
// The result should match the actual 12 closest
|
||||
Assert.Equal(expected12Closest.Count, result.Length);
|
||||
|
||||
// Verify all returned mobiles are in the expected 12 closest
|
||||
foreach (var mob in result)
|
||||
{
|
||||
Assert.Contains(mob, expected12Closest.Select(x => x.Mobile));
|
||||
}
|
||||
|
||||
// Verify they are sorted by distance (main invariant)
|
||||
for (var i = 0; i < result.Length - 1; i++)
|
||||
{
|
||||
var dist1 = result[i].GetDistanceToSqrt(center);
|
||||
var dist2 = result[i + 1].GetDistanceToSqrt(center);
|
||||
Assert.True(dist1 <= dist2, $"Mobiles not sorted by distance: {dist1} > {dist2}");
|
||||
}
|
||||
|
||||
// Verify we got the closest mobiles (not just any 12)
|
||||
var maxResultDistance = result.Max(m => m.GetDistanceToSqrt(center));
|
||||
var minExcludedDistance = allDistances.Skip(12).Any()
|
||||
? allDistances.Skip(12).Min(x => x.Distance)
|
||||
: double.MaxValue;
|
||||
Assert.True(maxResultDistance <= minExcludedDistance,
|
||||
$"Found a closer excluded mobile: max in result={maxResultDistance}, min excluded={minExcludedDistance}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
tracker?.Delete();
|
||||
foreach (var mob in mobiles)
|
||||
{
|
||||
mob?.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PlayerMobile CreatePlayerMobile(Map map, Point3D location)
|
||||
{
|
||||
var mobile = new PlayerMobile(World.NewMobile);
|
||||
mobile.DefaultMobileInit();
|
||||
mobile.MoveToWorld(location, map);
|
||||
return mobile;
|
||||
}
|
||||
|
||||
private static TestAnimal CreateAnimal(Map map, Point3D location)
|
||||
{
|
||||
var animal = new TestAnimal(World.NewMobile);
|
||||
animal.DefaultMobileInit();
|
||||
animal.MoveToWorld(location, map);
|
||||
return animal;
|
||||
}
|
||||
|
||||
private class TestAnimal : BaseCreature
|
||||
{
|
||||
public TestAnimal(Serial serial) : base(serial)
|
||||
{
|
||||
Body = 0xD8; // Llama body - an animal body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ public static class Tracking
|
|||
|
||||
public static void AddInfo(Mobile tracker, Mobile target)
|
||||
{
|
||||
var info = new TrackingInfo(tracker, target);
|
||||
var info = new TrackingInfo(target);
|
||||
_table[tracker] = info;
|
||||
}
|
||||
|
||||
|
|
@ -80,11 +80,9 @@ public static class Tracking
|
|||
public Point2D _location;
|
||||
public readonly Map _map;
|
||||
public readonly Mobile _target;
|
||||
public Mobile _tracker;
|
||||
|
||||
public TrackingInfo(Mobile tracker, Mobile target)
|
||||
public TrackingInfo(Mobile target)
|
||||
{
|
||||
_tracker = tracker;
|
||||
_target = target;
|
||||
_location = new Point2D(target);
|
||||
_map = target.Map;
|
||||
|
|
@ -164,7 +162,8 @@ public class TrackWhoGump : DynamicGump
|
|||
|
||||
from.CheckSkill(SkillName.Tracking, 21.1, 100.0); // Passive gain
|
||||
|
||||
var range = 10 + (int)(from.Skills.Tracking.Value / 10);
|
||||
// 10 tiles base + 10 tiles per 10 skill
|
||||
var range = 10 + (int)from.Skills.Tracking.Value / 10 * 10;
|
||||
|
||||
var mobs = GetClosestMobs(from, range, type);
|
||||
|
||||
|
|
@ -231,13 +230,19 @@ public class TrackWhoGump : DynamicGump
|
|||
var loc = from.Location;
|
||||
|
||||
// We only track the closest 12
|
||||
// Using a simple array with count is sufficient since we're single-threaded
|
||||
var mobs = new Mobile[MaxClosest];
|
||||
Span<double> distances = stackalloc double[MaxClosest];
|
||||
distances.Fill(double.MaxValue); // Fill with max values
|
||||
var total = 0;
|
||||
var maxDistance = double.MaxValue;
|
||||
var count = 0;
|
||||
|
||||
foreach (var m in from.GetMobilesInRange(range))
|
||||
foreach (var (m, minDistance) in from.GetMobilesInRangeByDistance(range))
|
||||
{
|
||||
if (count == MaxClosest && minDistance > maxDistance)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (m == from || Core.AOS && !m.Alive ||
|
||||
m.Hidden && m.AccessLevel != AccessLevel.Player && from.AccessLevel <= m.AccessLevel ||
|
||||
!IsValidMobileType(m, type) || !CheckDifficulty(from, m))
|
||||
|
|
@ -245,30 +250,51 @@ public class TrackWhoGump : DynamicGump
|
|||
continue;
|
||||
}
|
||||
|
||||
total++;
|
||||
|
||||
var distance = m.GetDistanceToSqrt(loc);
|
||||
for (var i = 0; i < MaxClosest; i++)
|
||||
|
||||
if (count >= MaxClosest && distance >= maxDistance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var searchLimit = Math.Min(count, MaxClosest - 1);
|
||||
var insertIndex = searchLimit;
|
||||
|
||||
for (var i = 0; i < searchLimit; i++)
|
||||
{
|
||||
if (distance < distances[i])
|
||||
{
|
||||
// Shift down the rest
|
||||
for (int j = MaxClosest - 1; j > i; j--)
|
||||
{
|
||||
mobs[j] = mobs[j - 1];
|
||||
distances[j] = distances[j - 1];
|
||||
}
|
||||
|
||||
mobs[i] = m;
|
||||
distances[i] = distance;
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Shift elements to make room for insertion
|
||||
if (insertIndex < searchLimit)
|
||||
{
|
||||
var shiftCount = searchLimit - insertIndex;
|
||||
Array.Copy(mobs, insertIndex, mobs, insertIndex + 1, shiftCount);
|
||||
distances.Slice(insertIndex, shiftCount).CopyTo(distances[(insertIndex + 1)..]);
|
||||
}
|
||||
|
||||
mobs[insertIndex] = m;
|
||||
distances[insertIndex] = distance;
|
||||
|
||||
if (count < MaxClosest)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == MaxClosest)
|
||||
{
|
||||
maxDistance = distances[MaxClosest - 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (total < MaxClosest)
|
||||
// Only resize if we found fewer than MaxClosest
|
||||
if (count < MaxClosest)
|
||||
{
|
||||
Array.Resize(ref mobs, total);
|
||||
Array.Resize(ref mobs, count);
|
||||
}
|
||||
|
||||
return mobs;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue