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

@ -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);
}
}