feat: add pathfinding
This commit is contained in:
140
Client/Infrastructure/Service/AsyncPathMover.cs
Normal file
140
Client/Infrastructure/Service/AsyncPathMover.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using Client.Application.ViewModels;
|
||||
using Client.Domain.DTO;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Enums;
|
||||
using Client.Domain.Service;
|
||||
using Client.Domain.ValueObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Client.Infrastructure.Service
|
||||
{
|
||||
public class AsyncPathMover : AsyncPathMoverInterface
|
||||
{
|
||||
private readonly WorldHandler worldHandler;
|
||||
private readonly PathfinderInterface pathfinder;
|
||||
private readonly int pathNumberOfAttempts;
|
||||
private readonly double nodeWaitingTime;
|
||||
private readonly int nodeDistanceTolerance;
|
||||
private readonly int nextNodeDistanceTolerance;
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
public ObservableCollection<PathSegment> Path { get; private set; } = new ObservableCollection<PathSegment>();
|
||||
|
||||
public async Task MoveUntilReachedAsync(Vector3 location)
|
||||
{
|
||||
var remainingAttempts = pathNumberOfAttempts;
|
||||
while (!await MoveAsync(location) && remainingAttempts > 0)
|
||||
{
|
||||
remainingAttempts--;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> MoveAsync(Vector3 location)
|
||||
{
|
||||
if (cancellationTokenSource != null)
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
cancellationTokenSource.Dispose();
|
||||
}
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancellationTokenSource.Token;
|
||||
|
||||
try
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Debug.WriteLine("Find path start");
|
||||
FindPath(location);
|
||||
Debug.WriteLine("Find path finish");
|
||||
|
||||
|
||||
foreach (var node in Path.ToList())
|
||||
{
|
||||
worldHandler.RequestMoveToLocation(node.To);
|
||||
|
||||
if (!WaitForNodeReaching(cancellationToken, node))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Path.Remove(node);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncPathMover(WorldHandler worldHandler, PathfinderInterface pathfinder, int pathNumberOfAttempts, double nodeWaitingTime, int nodeDistanceTolerance, int nextNodeDistanceTolerance)
|
||||
{
|
||||
this.worldHandler = worldHandler;
|
||||
this.pathfinder = pathfinder;
|
||||
this.pathNumberOfAttempts = pathNumberOfAttempts;
|
||||
this.nodeWaitingTime = nodeWaitingTime;
|
||||
this.nodeDistanceTolerance = nodeDistanceTolerance;
|
||||
this.nextNodeDistanceTolerance = nextNodeDistanceTolerance;
|
||||
}
|
||||
|
||||
private void FindPath(Vector3 location)
|
||||
{
|
||||
var hero = worldHandler.Hero;
|
||||
|
||||
Path.Clear();
|
||||
if (hero == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var path = pathfinder.FindPath(hero.Transform.Position, location);
|
||||
foreach (var segment in path)
|
||||
{
|
||||
Path.Add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
private bool WaitForNodeReaching(CancellationToken token, PathSegment node)
|
||||
{
|
||||
var hero = worldHandler.Hero;
|
||||
|
||||
var start = DateTime.Now;
|
||||
while (hero != null && !hero.Transform.Position.ApproximatelyEquals(node.To, nodeDistanceTolerance))
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
if (hero.Transform.Velocity.Equals(Vector3.Zero))
|
||||
{
|
||||
var elapsedSeconds = (DateTime.Now - start).TotalSeconds;
|
||||
if (hero.Transform.Position.ApproximatelyEquals(node.To, nextNodeDistanceTolerance))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (elapsedSeconds >= nodeWaitingTime)
|
||||
{
|
||||
Path.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Task.Delay(25);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
95
Client/Infrastructure/Service/L2jGeoDataPathfinder.cs
Normal file
95
Client/Infrastructure/Service/L2jGeoDataPathfinder.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Client.Domain.DTO;
|
||||
using Client.Domain.Service;
|
||||
using Client.Domain.ValueObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Infrastructure.Service
|
||||
{
|
||||
public class L2jGeoDataPathfinder : PathfinderInterface
|
||||
{
|
||||
[DllImport("L2JGeoDataPathFinder.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern uint FindPath(out IntPtr arrayPtr, string geoDataDirectory, float startX, float startY, float startZ, float endX, float endY, ushort maxPassableHeight);
|
||||
[DllImport("L2JGeoDataPathFinder.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern uint ReleasePath(IntPtr arrayPtr);
|
||||
|
||||
public L2jGeoDataPathfinder(string geodataDirectory, ushort maxPassableHeight)
|
||||
{
|
||||
this.geodataDirectory = geodataDirectory;
|
||||
this.maxPassableHeight = maxPassableHeight;
|
||||
}
|
||||
|
||||
public List<PathSegment> FindPath(Vector3 start, Vector3 end)
|
||||
{
|
||||
var arrayPtr = IntPtr.Zero;
|
||||
var size = FindPath(out arrayPtr, GetGeodataFullpath(), start.X, start.Y, start.Z, end.X, end.Y, maxPassableHeight);
|
||||
var originalArrayPtr = arrayPtr;
|
||||
|
||||
var nodes = new List<PathNode>();
|
||||
if (size > 0)
|
||||
{
|
||||
var entrySize = Marshal.SizeOf(typeof(PathNode));
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
var node = Marshal.PtrToStructure(arrayPtr, typeof(PathNode));
|
||||
if (node != null)
|
||||
{
|
||||
nodes.Add((PathNode)node);
|
||||
}
|
||||
arrayPtr = new IntPtr(arrayPtr.ToInt32() + entrySize);
|
||||
}
|
||||
ReleasePath(originalArrayPtr);
|
||||
}
|
||||
|
||||
return BuildPath(nodes);
|
||||
}
|
||||
|
||||
private List<PathSegment> BuildPath(List<PathNode> nodes)
|
||||
{
|
||||
var result = new List<PathSegment>();
|
||||
|
||||
var points = new List<Vector3>();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
points.Add(NodeToVector(node));
|
||||
}
|
||||
|
||||
for (var i = 0; i < points.Count - 1; i++)
|
||||
{
|
||||
var point = points[i];
|
||||
var nextPoint = points[i + 1];
|
||||
|
||||
result.Add(new PathSegment
|
||||
{
|
||||
From = point,
|
||||
To = nextPoint
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Vector3 NodeToVector(PathNode node)
|
||||
{
|
||||
var rnd = new Random();
|
||||
|
||||
return new Vector3(
|
||||
rnd.Next((int)node.minX, (int)node.maxX + 1),
|
||||
rnd.Next((int)node.minY, (int)node.maxY + 1),
|
||||
node.height
|
||||
);
|
||||
}
|
||||
|
||||
private string GetGeodataFullpath()
|
||||
{
|
||||
return System.IO.Directory.GetCurrentDirectory() + "/Assets/" + geodataDirectory + "/";
|
||||
}
|
||||
|
||||
private readonly string geodataDirectory;
|
||||
private readonly ushort maxPassableHeight;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user