feat: add pathfinding

This commit is contained in:
k0t9i 2023-10-29 20:55:36 +04:00
parent efffe8a7cb
commit 17a4f82f24
20 changed files with 577 additions and 48 deletions

View File

@ -19,6 +19,9 @@ using Client.Infrastructure.Helpers;
using Client.Domain.Events;
using Client.Infrastructure.Events;
using System;
using Client.Infrastructure.Service;
using System.Collections.Generic;
using System.Linq;
namespace Client
{
@ -105,7 +108,26 @@ namespace Client
.AddSingleton<SkillHandler>()
.AddSingleton<ItemHander>()
.AddSingleton<WorldHandler>()
.AddSingleton(
typeof(PathfinderInterface),
x => new L2jGeoDataPathfinder(
config.GetValue<string>("GeoDataDirectory") ?? "",
config.GetValue<ushort>("MaxPassableHeight")
)
)
.AddSingleton(
typeof(AsyncPathMoverInterface),
x => new AsyncPathMover(
x.GetRequiredService<WorldHandler>(),
x.GetRequiredService<PathfinderInterface>(),
config.GetValue<int>("PathNumberOfAttempts"),
config.GetValue<double>("NodeWaitingTime"),
config.GetValue<int>("NodeDistanceTolerance"),
config.GetValue<int>("NextNodeDistanceTolerance")
)
)
.AddSingleton<MainViewModel>();
}
}

View File

@ -220,6 +220,41 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Path=Path}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas ClipToBounds="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Line
X1="{Binding From.X}"
Y1="{Binding From.Y}"
X2="{Binding To.X}"
Y2="{Binding To.Y}"
Stroke="Red"
StrokeDashArray="1 3"
StrokeThickness="1"
/>
<Path
Stroke="Red"
StrokeThickness="1"
>
<Path.Data>
<EllipseGeometry
RadiusX="{Binding Radius,Mode=OneWay}"
RadiusY="{Binding Radius,Mode=OneWay}" />
</Path.Data>
<Path.RenderTransform>
<TranslateTransform X="{Binding To.X,Mode=OneWay}" Y="{Binding To.Y,Mode=OneWay}"/>
</Path.RenderTransform>
</Path>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right" Background="#66ffffff">
<Grid Margin="10 5">
<Grid.ColumnDefinitions>

View File

@ -39,12 +39,12 @@ namespace Client.Application.ViewModels
worldHandler.RequestAttackOrFollow(Id);
}
private void OnMouseRightClick(object? obj)
private async Task OnMouseRightClick(object? obj)
{
worldHandler.RequestMoveToEntity(Id);
await pathMover.MoveUntilReachedAsync(creature.Transform.Position);
}
public CreatureListViewModel(WorldHandler worldHandler, CreatureInterface creature, Hero hero)
public CreatureListViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, CreatureInterface creature, Hero hero)
{
creature.PropertyChanged += Creature_PropertyChanged;
creature.Transform.Position.PropertyChanged += Position_PropertyChanged;
@ -52,11 +52,12 @@ namespace Client.Application.ViewModels
hero.PropertyChanged += Hero_PropertyChanged;
MouseLeftClickCommand = new RelayCommand(OnMouseLeftClick);
MouseLeftDoubleClickCommand = new RelayCommand(OnMouseLeftDoubleClick);
MouseRightClickCommand = new RelayCommand(OnMouseRightClick);
MouseRightClickCommand = new RelayCommand(async (o) => await OnMouseRightClick(o));
this.creature = creature;
this.hero = hero;
this.worldHandler = worldHandler;
this.pathMover = pathMover;
}
private void HeroPosition_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@ -94,5 +95,6 @@ namespace Client.Application.ViewModels
private readonly CreatureInterface creature;
private readonly Hero hero;
private readonly WorldHandler worldHandler;
private readonly AsyncPathMoverInterface pathMover;
}
}

View File

@ -77,16 +77,17 @@ namespace Client.Application.ViewModels
worldHandler.RequestAttackOrFollow(Id);
}
private void OnMouseRightClick(object? obj)
private async Task OnMouseRightClick(object? obj)
{
worldHandler.RequestMoveToEntity(Id);
await pathMover.MoveUntilReachedAsync(creature.Transform.Position);
}
public CreatureMapViewModel(WorldHandler worldHandler, CreatureInterface creature, Hero hero)
public CreatureMapViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, CreatureInterface creature, Hero hero)
{
this.creature = creature;
this.hero = hero;
this.worldHandler = worldHandler;
this.pathMover = pathMover;
creature.PropertyChanged += Creature_PropertyChanged;
creature.Transform.PropertyChanged += Transform_PropertyChanged;
creature.Transform.Position.PropertyChanged += Position_PropertyChanged;
@ -95,7 +96,7 @@ namespace Client.Application.ViewModels
hero.PropertyChanged += Hero_PropertyChanged;
MouseLeftClickCommand = new RelayCommand(OnMouseLeftClick);
MouseLeftDoubleClickCommand = new RelayCommand(OnMouseLeftDoubleClick);
MouseRightClickCommand = new RelayCommand(OnMouseRightClick);
MouseRightClickCommand = new RelayCommand(async (o) => await OnMouseRightClick(o));
}
private void VitalStats_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@ -149,6 +150,7 @@ namespace Client.Application.ViewModels
private readonly CreatureInterface creature;
private readonly Hero hero;
private readonly WorldHandler worldHandler;
private readonly AsyncPathMoverInterface pathMover;
private float scale = 1;
private static readonly float MAX_RADIUS = 10;
private static readonly float MIN_RADIUS = 2;

View File

@ -74,21 +74,22 @@ namespace Client.Application.ViewModels
{
worldHandler.RequestPickUp(Id);
}
private void OnMouseRightClick(object? obj)
private async Task OnMouseRightClick(object? obj)
{
worldHandler.RequestMoveToEntity(Id);
await pathMover.MoveUntilReachedAsync(drop.Transform.Position);
}
public DropListViewModel(WorldHandler worldHandler, Drop drop, Hero hero)
public DropListViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, Drop drop, Hero hero)
{
this.drop = drop;
this.hero = hero;
this.worldHandler = worldHandler;
this.pathMover = pathMover;
drop.PropertyChanged += Drop_PropertyChanged;
drop.Transform.Position.PropertyChanged += DropPosition_PropertyChanged;
hero.Transform.Position.PropertyChanged += HeroPosition_PropertyChanged;
MouseLeftClickCommand = new RelayCommand(OnMouseLeftClick);
MouseRightClickCommand = new RelayCommand(OnMouseRightClick);
MouseRightClickCommand = new RelayCommand(async (o) => await OnMouseRightClick(o));
}
private void HeroPosition_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@ -126,5 +127,6 @@ namespace Client.Application.ViewModels
private readonly Drop drop;
private readonly Hero hero;
private readonly WorldHandler worldHandler;
private readonly AsyncPathMoverInterface pathMover;
}
}

View File

@ -56,21 +56,22 @@ namespace Client.Application.ViewModels
{
worldHandler.RequestPickUp(Id);
}
private void OnMouseRightClick(object? obj)
private async Task OnMouseRightClick(object? obj)
{
worldHandler.RequestMoveToEntity(Id);
await pathMover.MoveUntilReachedAsync(drop.Transform.Position);
}
public DropMapViewModel(WorldHandler worldHandler, Drop drop, Hero hero)
public DropMapViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, Drop drop, Hero hero)
{
this.drop = drop;
this.hero = hero;
this.worldHandler = worldHandler;
this.pathMover = pathMover;
drop.PropertyChanged += Creature_PropertyChanged;
drop.Transform.Position.PropertyChanged += Position_PropertyChanged;
hero.Transform.Position.PropertyChanged += HeroPosition_PropertyChanged;
MouseLeftClickCommand = new RelayCommand(OnMouseLeftClick);
MouseRightClickCommand = new RelayCommand(OnMouseRightClick);
MouseRightClickCommand = new RelayCommand(async (o) => await OnMouseRightClick(o));
}
private void HeroPosition_PropertyChanged(object? sender, PropertyChangedEventArgs e)
@ -94,6 +95,7 @@ namespace Client.Application.ViewModels
private readonly Drop drop;
private readonly Hero hero;
private readonly WorldHandler worldHandler;
private readonly AsyncPathMoverInterface pathMover;
private float scale = 1;
private static readonly float MAX_RADIUS = 8;
private static readonly float MIN_RADIUS = 2;

View File

@ -59,7 +59,7 @@ namespace Client.Application.ViewModels
{
if (hero != null)
{
Creatures.Add(new CreatureListViewModel(worldHandler, @event.Creature, hero));
Creatures.Add(new CreatureListViewModel(worldHandler, pathMover, @event.Creature, hero));
AddCreature(@event.Creature);
}
}
@ -74,8 +74,8 @@ namespace Client.Application.ViewModels
{
if (hero != null)
{
Drops.Add(new DropListViewModel(worldHandler, @event.Drop, hero));
Map.Drops.Add(new DropMapViewModel(worldHandler, @event.Drop, hero));
Drops.Add(new DropListViewModel(worldHandler, pathMover, @event.Drop, hero));
Map.Drops.Add(new DropMapViewModel(worldHandler, pathMover, @event.Drop, hero));
}
}
@ -136,7 +136,7 @@ namespace Client.Application.ViewModels
{
if (hero != null)
{
Map.Creatures.Add(new CreatureMapViewModel(worldHandler, creature, hero));
Map.Creatures.Add(new CreatureMapViewModel(worldHandler, pathMover, creature, hero));
}
}
@ -145,10 +145,11 @@ namespace Client.Application.ViewModels
Map.Creatures.RemoveAll(x => x.Id == id);
}
public MainViewModel(WorldHandler worldHandler)
public MainViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover)
{
this.worldHandler = worldHandler;
Map = new MapViewModel(worldHandler);
this.pathMover = pathMover;
Map = new MapViewModel(pathMover);
}
public ObservableCollection<ChatMessageViewModel> ChatMessages { get; } = new ObservableCollection<ChatMessageViewModel>();
@ -162,5 +163,6 @@ namespace Client.Application.ViewModels
public MapViewModel Map { get; private set; }
public Hero? hero;
private readonly WorldHandler worldHandler;
private readonly AsyncPathMoverInterface pathMover;
}
}

View File

@ -14,6 +14,10 @@ using System.Collections.Specialized;
using Client.Application.Commands;
using System.Reflection.Metadata;
using System.Windows;
using Client.Infrastructure.Service;
using System.Windows.Data;
using Client.Domain.DTO;
using System.Windows.Documents;
namespace Client.Application.ViewModels
{
@ -138,11 +142,17 @@ namespace Client.Application.ViewModels
drop.Scale = scale;
drop.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
}
foreach (var node in Path)
{
node.Scale = scale;
node.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
}
}
}
public ICommand MouseLeftClickCommand { get; }
private void OnLeftMouseClick(object? obj)
private async Task OnLeftMouseClick(object? obj)
{
if (obj == null)
{
@ -159,7 +169,8 @@ namespace Client.Application.ViewModels
(float)(mousePos.Y - ViewportHeight / 2) * scale + hero.Transform.Position.Y,
hero.Transform.Position.Z
);
worldHandler.RequestMoveToLocation(location);
await pathMover.MoveUntilReachedAsync(location);
}
public void OnMouseWheel(object sender, MouseWheelEventArgs e)
@ -189,13 +200,58 @@ namespace Client.Application.ViewModels
mousePosition.Y = (float)(mousePos.Y - ViewportHeight / 2) * scale + hero.Transform.Position.Y;
}
public MapViewModel(WorldHandler worldHandler)
public MapViewModel(AsyncPathMoverInterface pathMover)
{
Creatures.CollectionChanged += Creatures_CollectionChanged;
Drops.CollectionChanged += Drops_CollectionChanged;
this.worldHandler = worldHandler;
MouseLeftClickCommand = new RelayCommand(OnLeftMouseClick);
Path.CollectionChanged += Path_CollectionChanged;
MouseLeftClickCommand = new RelayCommand(async (o) => await OnLeftMouseClick(o));
mousePosition.PropertyChanged += MousePosition_PropertyChanged;
BindingOperations.EnableCollectionSynchronization(Path, pathCollectionLock);
this.pathMover = pathMover;
this.pathMover.Path.CollectionChanged += PathMover_Path_CollectionChanged;
}
private void PathMover_Path_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
lock(pathCollectionLock)
{
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
{
if (hero != null)
{
foreach (var item in e.NewItems)
{
var node = (PathSegment)item;
Path.Add(new PathNodeViewModel(node.From, node.To, hero));
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
{
foreach (var item in e.OldItems)
{
Path.RemoveAt(0);
}
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
Path.Clear();
}
}
}
private void Path_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
{
foreach (var item in e.NewItems)
{
var node = (PathNodeViewModel)item;
node.Scale = scale;
node.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
}
}
}
private void MousePosition_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@ -232,9 +288,11 @@ namespace Client.Application.ViewModels
public ObservableCollection<MapBlockViewModel> Blocks { get; } = new ObservableCollection<MapBlockViewModel>();
public ObservableCollection<CreatureMapViewModel> Creatures { get; } = new ObservableCollection<CreatureMapViewModel>();
public ObservableCollection<DropMapViewModel> Drops { get; } = new ObservableCollection<DropMapViewModel>();
public ObservableCollection<PathNodeViewModel> Path { get; } = new ObservableCollection<PathNodeViewModel>();
public readonly static float MIN_SCALE = 1;
public readonly static float MAX_SCALE = 64;
private readonly AsyncPathMoverInterface pathMover;
private MapImageSelector selector = new MapImageSelector();
private Dictionary<uint, MapBlockViewModel> blocks = new Dictionary<uint, MapBlockViewModel>();
private Hero? hero;
@ -242,6 +300,6 @@ namespace Client.Application.ViewModels
private double viewportWidth = 0;
private double viewportHeight = 0;
private Vector3 mousePosition = new Vector3(0, 0, 0);
private readonly WorldHandler worldHandler;
private object pathCollectionLock = new object();
}
}

View File

@ -0,0 +1,80 @@
using Client.Application.Commands;
using Client.Domain.Common;
using Client.Domain.Entities;
using Client.Domain.Service;
using Client.Domain.ValueObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Application.ViewModels
{
public class PathNodeViewModel : ObservableObject
{
public Vector3 From => new Vector3(
(from.X - hero.Transform.Position.X) / scale + (VieportSize.X / 2),
(from.Y - hero.Transform.Position.Y) / scale + (VieportSize.Y / 2),
0
);
public Vector3 To => new Vector3(
(to.X - hero.Transform.Position.X) / scale + (VieportSize.X / 2),
(to.Y - hero.Transform.Position.Y) / scale + (VieportSize.Y / 2),
0
);
public float Radius => MAX_RADIUS - (1 / MapViewModel.MIN_SCALE - 1 / scale) / (1 / MapViewModel.MIN_SCALE - 1 / MapViewModel.MAX_SCALE) * (MAX_RADIUS - MIN_RADIUS);
public float Scale
{
get => scale;
set
{
if (scale != value)
{
scale = value;
OnPropertyChanged("From");
OnPropertyChanged("To");
OnPropertyChanged("Radius");
}
}
}
public Vector3 VieportSize
{
get => vieportSize;
set
{
if (vieportSize != value)
{
vieportSize = value;
OnPropertyChanged("From");
OnPropertyChanged("To");
}
}
}
public PathNodeViewModel(Vector3 from, Vector3 to, Hero hero)
{
this.from = from;
this.to = to;
this.hero = hero;
hero.Transform.Position.PropertyChanged += HeroPosition_PropertyChanged;
}
private void HeroPosition_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged("From");
OnPropertyChanged("To");
}
private readonly Vector3 from;
private readonly Vector3 to;
private readonly Hero hero;
private float scale = 1;
private static readonly float MAX_RADIUS = 10;
private static readonly float MIN_RADIUS = 2;
private Vector3 vieportSize = new Vector3(0, 0, 0);
}
}

View File

@ -0,0 +1,15 @@
using Client.Domain.ValueObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.DTO
{
public class PathSegment
{
public Vector3 From = new Vector3(0, 0, 0);
public Vector3 To = new Vector3(0, 0, 0);
}
}

View File

@ -0,0 +1,18 @@
using Client.Domain.DTO;
using Client.Domain.ValueObjects;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.Service
{
public interface AsyncPathMoverInterface
{
public ObservableCollection<PathSegment> Path { get; }
public Task<bool> MoveAsync(Vector3 location);
public Task MoveUntilReachedAsync(Vector3 location);
}
}

View File

@ -0,0 +1,15 @@
using Client.Domain.DTO;
using Client.Domain.ValueObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.Service
{
public interface PathfinderInterface
{
public List<PathSegment> FindPath(Vector3 start, Vector3 end);
}
}

View File

@ -27,6 +27,8 @@ namespace Client.Domain.Service
EventHandlerInterface<ItemCreatedEvent>,
EventHandlerInterface<ItemDeletedEvent>
{
public Hero? Hero => hero;
public void RequestMoveToLocation(Vector3 location)
{
if (hero == null)
@ -37,24 +39,6 @@ namespace Client.Domain.Service
SendMessage(OutgoingMessageTypeEnum.Move, location);
}
public void RequestMoveToEntity(uint id)
{
if (hero == null)
{
return;
}
if (!creatures.ContainsKey(id) && !drops.ContainsKey(id))
{
Debug.WriteLine("RequestMoveToEntity: entity " + id + " not found");
return;
}
var position = creatures.ContainsKey(id) ? creatures[id].Transform.Position : drops[id].Transform.Position;
RequestMoveToLocation(position);
}
public void RequestAcquireTarget(uint id)
{
if (hero == null)

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.ValueObjects
{
public struct PathNode
{
public readonly uint minX;
public readonly uint minY;
public readonly uint maxX;
public readonly uint maxY;
public readonly short height;
}
}

View File

@ -11,7 +11,7 @@ namespace Client.Domain.ValueObjects
public float X { get => x; set { if (value != x) { x = value; OnPropertyChanged("X"); } } }
public float Y { get => y; set { if (value != y) { y = value; OnPropertyChanged("Y"); } } }
public float Z { get => z; set { if (value != z) { z = value; OnPropertyChanged("X"); } } }
public float Z { get => z; set { if (value != z) { z = value; OnPropertyChanged("Z"); } } }
public Vector3(float x, float y, float z)
{
@ -29,5 +29,34 @@ namespace Client.Domain.ValueObjects
{
return MathF.Sqrt(HorizontalSqrDistance(other));
}
public override bool Equals(object? other)
{
if (!(other is Vector3))
{
return false;
}
var obj = (Vector3)other;
return MathF.Abs(x - obj.x) < float.Epsilon && MathF.Abs(y - obj.y) < float.Epsilon && MathF.Abs(z - obj.z) < float.Epsilon;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public bool ApproximatelyEquals(Vector3 other, float epsilon, bool withZ = false)
{
var equals = MathF.Abs(x - other.x) < epsilon && MathF.Abs(y - other.y) < epsilon;
if (withZ)
{
equals = equals && MathF.Abs(z - other.z) < epsilon;
}
return equals;
}
public static readonly Vector3 Zero = new Vector3(0, 0, 0);
}
}

View 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;
}
}
}

View 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;
}
}

View File

@ -0,0 +1,8 @@
{
"profiles": {
"Client": {
"commandName": "Project",
"nativeDebugging": true
}
}
}

Binary file not shown.

View File

@ -237,9 +237,11 @@
<ItemGroup>
<ClCompile Include="Domain\Entities\ChatMessage.cpp">
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="Domain\Helpers\HashCombiner.cpp">
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>