feat: add combat and deleveling AI
This commit is contained in:
parent
bdd026519f
commit
2943f7a50b
@ -1,7 +1,8 @@
|
||||
<Application x:Class="Client.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Client">
|
||||
xmlns:local="clr-namespace:Client"
|
||||
ShutdownMode="OnMainWindowClose">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
|
@ -22,6 +22,9 @@ using System;
|
||||
using Client.Infrastructure.Service;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Client.Domain.AI;
|
||||
using Client.Infrastructure.AI.IO;
|
||||
using Client.Domain.AI.IO;
|
||||
|
||||
namespace Client
|
||||
{
|
||||
@ -48,10 +51,10 @@ namespace Client
|
||||
var startupForm = AppHost.Services.GetRequiredService<MainWindow>();
|
||||
startupForm.Show();
|
||||
|
||||
var application = AppHost.Services.GetRequiredService<Bot>();
|
||||
application.StartAsync();
|
||||
|
||||
base.OnStartup(e);
|
||||
|
||||
var application = AppHost.Services.GetRequiredService<Bot>();
|
||||
await application.StartAsync();
|
||||
}
|
||||
|
||||
protected override async void OnExit(ExitEventArgs e)
|
||||
@ -67,6 +70,8 @@ namespace Client
|
||||
.AddJsonFile("config.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile("Assets/data/experience.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile("Assets/data/npcInfo.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile("Assets/data/itemInfo.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile("Assets/data/skillInfo.json", optional: false, reloadOnChange: true)
|
||||
.Build();
|
||||
|
||||
services
|
||||
@ -90,6 +95,8 @@ namespace Client
|
||||
)
|
||||
.AddSingleton(typeof(ExperienceHelperInterface), typeof(ConfigurationExperienceHelper))
|
||||
.AddSingleton(typeof(NpcInfoHelperInterface), typeof(ConfigurationNpcInfoHelper))
|
||||
.AddSingleton(typeof(ItemInfoHelperInterface), typeof(ConfigurationItemInfoHelper))
|
||||
.AddSingleton(typeof(SkillInfoHelperInterface), typeof(ConfigurationSkillInfoHelper))
|
||||
.AddSingleton(typeof(EventBusInterface), typeof(InMemoryEventBus))
|
||||
|
||||
.AddTransient(typeof(EntityFactoryInterface<Entity>), typeof(EntityFactory<Entity>))
|
||||
@ -129,7 +136,23 @@ namespace Client
|
||||
)
|
||||
)
|
||||
|
||||
.AddSingleton<MainViewModel>();
|
||||
.AddSingleton<Config>()
|
||||
.AddSingleton<TransitionBuilderLocator>()
|
||||
.AddSingleton(
|
||||
typeof(AIInterface),
|
||||
x => new AI(
|
||||
x.GetRequiredService<WorldHandler>(),
|
||||
x.GetRequiredService<Config>(),
|
||||
x.GetRequiredService<AsyncPathMoverInterface>(),
|
||||
x.GetRequiredService<TransitionBuilderLocator>()
|
||||
)
|
||||
)
|
||||
|
||||
.AddTransient(typeof(ConfigSerializerInterface), typeof(JsonConfigSerializer))
|
||||
.AddTransient(typeof(ConfigDeserializerInterface), typeof(JsonConfigDeserializer))
|
||||
|
||||
.AddSingleton<MainViewModel>()
|
||||
.AddSingleton<AIConfigViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,18 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Grid DataContext="{Binding CombatZone}" ClipToBounds="True">
|
||||
<Path StrokeThickness="2" Fill="#1100ff00" Stroke="#3300ff00" >
|
||||
<Path.Data>
|
||||
<EllipseGeometry
|
||||
RadiusX="{Binding Radius,Mode=OneWay}"
|
||||
RadiusY="{Binding Radius,Mode=OneWay}"/>
|
||||
</Path.Data>
|
||||
<Path.RenderTransform>
|
||||
<TranslateTransform X="{Binding Center.X,Mode=OneWay}" Y="{Binding Center.Y,Mode=OneWay}"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
<ItemsControl ItemsSource="{Binding Path=Creatures}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
@ -141,9 +153,17 @@
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Type,Mode=OneWay}" Value="NPC">
|
||||
<Setter TargetName="CreatureBody" Property="Stroke" Value="DarkGreen" />
|
||||
<Setter TargetName="CreatureDirection" Property="Stroke" Value="DarkGreen" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsHostile,Mode=OneWay}" Value="True">
|
||||
<Setter TargetName="CreatureBody" Property="Stroke" Value="LimeGreen" />
|
||||
<Setter TargetName="CreatureDirection" Property="Stroke" Value="LimeGreen" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsDead,Mode=OneWay}" Value="True">
|
||||
<Setter TargetName="CreatureBody" Property="Stroke" Value="Gray" />
|
||||
<Setter TargetName="CreatureDirection" Property="Stroke" Value="Gray" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type,Mode=OneWay}" Value="Player">
|
||||
<Setter TargetName="CreatureBody" Property="Stroke" Value="Blue" />
|
||||
<Setter TargetName="CreatureDirection" Property="Stroke" Value="Blue" />
|
||||
@ -156,6 +176,10 @@
|
||||
<Setter TargetName="CreatureBody" Property="Stroke" Value="Red" />
|
||||
<Setter TargetName="CreatureDirection" Property="Stroke" Value="Red" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsSweepable,Mode=OneWay}" Value="True">
|
||||
<Setter TargetName="CreatureBody" Property="Stroke" Value="Magenta" />
|
||||
<Setter TargetName="CreatureDirection" Property="Stroke" Value="Magenta" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsTarget,Mode=OneWay}" Value="True">
|
||||
<Setter TargetName="CreatureName" Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
|
102
Client/Application/Components/MultipleObjectSelector.xaml
Normal file
102
Client/Application/Components/MultipleObjectSelector.xaml
Normal file
@ -0,0 +1,102 @@
|
||||
<UserControl x:Class="Client.Application.Components.MultipleObjectSelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Client.Application.Components"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
mc:Ignorable="d"
|
||||
x:Name="root">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{Binding Header,ElementName=root}" />
|
||||
<StackPanel Grid.Row="1" Grid.Column="0">
|
||||
<TextBox
|
||||
x:Name="sourceSearch"
|
||||
HorizontalAlignment="Stretch">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="KeyUp">
|
||||
<i:InvokeCommandAction
|
||||
Command="{x:Static local:MultipleObjectSelector.SearchSourceCommand}"
|
||||
CommandParameter="{Binding Text,ElementName=sourceSearch}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</TextBox>
|
||||
<ListBox
|
||||
ItemsSource="{Binding Source,ElementName=root}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Height="100"
|
||||
Width="Auto"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
x:Name="source">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}">
|
||||
<TextBlock.InputBindings>
|
||||
<MouseBinding
|
||||
MouseAction="LeftDoubleClick"
|
||||
Command="{x:Static local:MultipleObjectSelector.AddItemCommand}"
|
||||
CommandParameter="{Binding SelectedValue,ElementName=source}"/>
|
||||
</TextBlock.InputBindings>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="2" Grid.Column="0">
|
||||
<Button
|
||||
Width="20"
|
||||
Command="{x:Static local:MultipleObjectSelector.AddItemCommand}"
|
||||
CommandParameter="{Binding SelectedValue,ElementName=source}"
|
||||
>↓</Button>
|
||||
<Button
|
||||
Width="20"
|
||||
Command="{x:Static local:MultipleObjectSelector.RemoveItemCommand}"
|
||||
CommandParameter="{Binding SelectedValue,ElementName=target}"
|
||||
>↑</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="3" Grid.Column="0">
|
||||
<TextBox
|
||||
x:Name="targetSearch"
|
||||
HorizontalAlignment="Stretch">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="KeyUp">
|
||||
<i:InvokeCommandAction
|
||||
Command="{x:Static local:MultipleObjectSelector.SearchTargetCommand}"
|
||||
CommandParameter="{Binding Text,ElementName=targetSearch}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</TextBox>
|
||||
<ListBox
|
||||
ItemsSource="{Binding Target,ElementName=root}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Height="100"
|
||||
Width="Auto"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
x:Name="target">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}">
|
||||
<TextBlock.InputBindings>
|
||||
<MouseBinding
|
||||
MouseAction="LeftDoubleClick"
|
||||
Command="{x:Static local:MultipleObjectSelector.RemoveItemCommand}"
|
||||
CommandParameter="{Binding SelectedValue,ElementName=target}"/>
|
||||
</TextBlock.InputBindings>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
109
Client/Application/Components/MultipleObjectSelector.xaml.cs
Normal file
109
Client/Application/Components/MultipleObjectSelector.xaml.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using Client.Domain.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Client.Application.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MobSelector.xaml
|
||||
/// </summary>
|
||||
public partial class MultipleObjectSelector : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty SourceProperty =
|
||||
DependencyProperty.Register("Source", typeof(ICollection<ObjectInfo>), typeof(MultipleObjectSelector), new PropertyMetadata(default(ICollection<ObjectInfo>)));
|
||||
public ICollection<ObjectInfo> Source
|
||||
{
|
||||
get { return (ICollection<ObjectInfo>)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty TargetProperty =
|
||||
DependencyProperty.Register("Target", typeof(ICollection<ObjectInfo>), typeof(MultipleObjectSelector), new PropertyMetadata(default(ICollection<ObjectInfo>)));
|
||||
public ICollection<ObjectInfo> Target
|
||||
{
|
||||
get { return (ICollection<ObjectInfo>)GetValue(TargetProperty); }
|
||||
set { SetValue(TargetProperty, value); }
|
||||
}
|
||||
public string Header
|
||||
{
|
||||
get { return (string)GetValue(HeaderProperty); }
|
||||
set { SetValue(HeaderProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty HeaderProperty =
|
||||
DependencyProperty.Register("Header", typeof(string), typeof(MultipleObjectSelector), new PropertyMetadata(""));
|
||||
|
||||
public static RoutedUICommand AddItemCommand { get; } = new RoutedUICommand(
|
||||
"Add item",
|
||||
nameof(AddItemCommand),
|
||||
typeof(MultipleObjectSelector)
|
||||
);
|
||||
public static RoutedUICommand RemoveItemCommand { get; } = new RoutedUICommand(
|
||||
"Remove item",
|
||||
nameof(RemoveItemCommand),
|
||||
typeof(MultipleObjectSelector)
|
||||
);
|
||||
public static RoutedUICommand SearchSourceCommand { get; } = new RoutedUICommand(
|
||||
"Search in source",
|
||||
nameof(SearchSourceCommand),
|
||||
typeof(MultipleObjectSelector)
|
||||
);
|
||||
public static RoutedUICommand SearchTargetCommand { get; } = new RoutedUICommand(
|
||||
"Search in target",
|
||||
nameof(SearchTargetCommand),
|
||||
typeof(MultipleObjectSelector)
|
||||
);
|
||||
|
||||
|
||||
public MultipleObjectSelector()
|
||||
{
|
||||
InitializeComponent();
|
||||
CommandBindings.Add(new CommandBinding(AddItemCommand, AddItem));
|
||||
CommandBindings.Add(new CommandBinding(RemoveItemCommand, RemoveItem));
|
||||
CommandBindings.Add(new CommandBinding(SearchSourceCommand, SearchSource));
|
||||
CommandBindings.Add(new CommandBinding(SearchTargetCommand, SearchTarget));
|
||||
}
|
||||
|
||||
private void AddItem(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var item = e.Parameter as ObjectInfo;
|
||||
if (item != null)
|
||||
{
|
||||
Source.Remove(item);
|
||||
Target.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveItem(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var item = e.Parameter as ObjectInfo;
|
||||
if (item != null)
|
||||
{
|
||||
Target.Remove(item);
|
||||
Source.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchSource(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var searchPredicate = (string)e.Parameter ?? null;
|
||||
|
||||
if (searchPredicate != null)
|
||||
{
|
||||
CollectionViewSource.GetDefaultView(Source).Filter = item => (item as ObjectInfo)?.Name.Contains(searchPredicate, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchTarget(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var searchPredicate = (string)e.Parameter ?? null;
|
||||
|
||||
if (searchPredicate != null)
|
||||
{
|
||||
CollectionViewSource.GetDefaultView(Target).Filter = item => (item as ObjectInfo)?.Name.Contains(searchPredicate, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
Client/Application/Components/ObjectSelector.xaml
Normal file
30
Client/Application/Components/ObjectSelector.xaml
Normal file
@ -0,0 +1,30 @@
|
||||
<UserControl x:Class="Client.Application.Components.ObjectSelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Client.Application.Components"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
mc:Ignorable="d"
|
||||
x:Name="root">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Label Content="{Binding Header,ElementName=root}" Grid.Row="0" Grid.Column="0"/>
|
||||
<ComboBox
|
||||
Grid.Row="1" Grid.Column="0"
|
||||
SelectedValuePath="Id"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Source,ElementName=root}"
|
||||
SelectedValue="{Binding SelectedValue,ElementName=root}"
|
||||
>
|
||||
<ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ComboBox.ItemsPanel>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</UserControl>
|
55
Client/Application/Components/ObjectSelector.xaml.cs
Normal file
55
Client/Application/Components/ObjectSelector.xaml.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Client.Application.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for SearchableCombobox.xaml
|
||||
/// </summary>
|
||||
public partial class ObjectSelector : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty SourceProperty =
|
||||
DependencyProperty.Register("Source", typeof(ICollection<ObjectInfo>), typeof(ObjectSelector), new PropertyMetadata(default(ICollection<ObjectInfo>)));
|
||||
public ICollection<ObjectInfo> Source
|
||||
{
|
||||
get { return (ICollection<ObjectInfo>)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedValueProperty =
|
||||
DependencyProperty.Register("SelectedValue", typeof(object), typeof(ObjectSelector), new PropertyMetadata(default(object)));
|
||||
public object SelectedValue
|
||||
{
|
||||
get { return GetValue(SelectedValueProperty); }
|
||||
set { SetValue(SelectedValueProperty, value); }
|
||||
}
|
||||
|
||||
public string Header
|
||||
{
|
||||
get { return (string)GetValue(HeaderProperty); }
|
||||
set { SetValue(HeaderProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty HeaderProperty =
|
||||
DependencyProperty.Register("Header", typeof(string), typeof(ObjectSelector), new PropertyMetadata(""));
|
||||
|
||||
public ObjectSelector()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
37
Client/Application/Services/EqualityConverter.cs
Normal file
37
Client/Application/Services/EqualityConverter.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Client.Application.Services
|
||||
{
|
||||
public class EqualityConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstValue = values.First();
|
||||
for (var i = 1; i < values.Length; i++)
|
||||
{
|
||||
if (!firstValue.Equals(values[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
89
Client/Application/ViewModels/AICombatZoneMapViewModel.cs
Normal file
89
Client/Application/ViewModels/AICombatZoneMapViewModel.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using Client.Domain.AI.Combat;
|
||||
using Client.Domain.Common;
|
||||
using Client.Domain.Entities;
|
||||
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 AICombatZoneMapViewModel : ObservableObject
|
||||
{
|
||||
public void MapUpdated(float scale, float viewportWidth, float viewportHeight)
|
||||
{
|
||||
Scale = scale;
|
||||
VieportSize = new Vector3(viewportWidth, viewportHeight, 0);
|
||||
}
|
||||
|
||||
public AICombatZoneMapViewModel(CombatZone combatZone, Hero hero)
|
||||
{
|
||||
this.combatZone = combatZone;
|
||||
this.hero = hero;
|
||||
|
||||
hero.Transform.Position.PropertyChanged += HeroPosition_PropertyChanged;
|
||||
combatZone.PropertyChanged += CombatZone_PropertyChanged;
|
||||
combatZone.Center.PropertyChanged += CombatZoneCenter_PropertyChanged;
|
||||
}
|
||||
|
||||
private void CombatZoneCenter_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged("Center");
|
||||
}
|
||||
|
||||
private void CombatZone_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "Radius")
|
||||
{
|
||||
OnPropertyChanged("Radius");
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Center => new Vector3(
|
||||
(combatZone.Center.X - hero.Transform.Position.X) / scale + (VieportSize.X / 2),
|
||||
(combatZone.Center.Y - hero.Transform.Position.Y) / scale + (VieportSize.Y / 2),
|
||||
0
|
||||
);
|
||||
|
||||
public float Radius => combatZone.Radius / scale;
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get => scale;
|
||||
set
|
||||
{
|
||||
if (scale != value)
|
||||
{
|
||||
scale = value;
|
||||
OnPropertyChanged("Center");
|
||||
OnPropertyChanged("Radius");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 VieportSize
|
||||
{
|
||||
get => vieportSize;
|
||||
set
|
||||
{
|
||||
if (vieportSize != value)
|
||||
{
|
||||
vieportSize = value;
|
||||
OnPropertyChanged("Center");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HeroPosition_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged("Center");
|
||||
}
|
||||
|
||||
private readonly CombatZone combatZone;
|
||||
private readonly Hero hero;
|
||||
private float scale = 1;
|
||||
private Vector3 vieportSize = new Vector3(0, 0, 0);
|
||||
}
|
||||
}
|
344
Client/Application/ViewModels/AIConfigViewModel.cs
Normal file
344
Client/Application/ViewModels/AIConfigViewModel.cs
Normal file
@ -0,0 +1,344 @@
|
||||
using Client.Application.Commands;
|
||||
using Client.Application.Components;
|
||||
using Client.Application.Views;
|
||||
using Client.Domain.AI;
|
||||
using Client.Domain.AI.IO;
|
||||
using Client.Domain.Common;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Events;
|
||||
using Client.Domain.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Client.Application.ViewModels
|
||||
{
|
||||
public class AIConfigViewModel :
|
||||
ObservableObject,
|
||||
EventHandlerInterface<HeroCreatedEvent>,
|
||||
EventHandlerInterface<HeroDeletedEvent>
|
||||
{
|
||||
public void Handle(HeroCreatedEvent @event)
|
||||
{
|
||||
hero = @event.Hero;
|
||||
}
|
||||
|
||||
public void Handle(HeroDeletedEvent @event)
|
||||
{
|
||||
hero = null;
|
||||
}
|
||||
|
||||
public class SkillCondition : ObservableObject
|
||||
{
|
||||
private uint id;
|
||||
private byte maxTargetPercentHp = 100;
|
||||
private byte minPlayerPercentMp = 0;
|
||||
private byte maxPlayerPercentHp = 100;
|
||||
|
||||
public uint Id { get => id; set { if (value != id) { id = value; OnPropertyChanged(); } } }
|
||||
public byte MaxTargetPercentHp { get => maxTargetPercentHp; set { if (value != maxTargetPercentHp) { maxTargetPercentHp = value; OnPropertyChanged(); } } }
|
||||
public byte MinPlayerPercentMp { get => minPlayerPercentMp; set { if (value != minPlayerPercentMp) { minPlayerPercentMp = value; OnPropertyChanged(); } } }
|
||||
public byte MaxPlayerPercentHp { get => maxPlayerPercentHp; set { if (value != maxPlayerPercentHp) { maxPlayerPercentHp = value; OnPropertyChanged(); } } }
|
||||
}
|
||||
|
||||
public class CombatZone : ObservableObject
|
||||
{
|
||||
private float x = 0;
|
||||
private float y = 0;
|
||||
private float radius = 0;
|
||||
|
||||
public float X { get => x; set { if (value != x) { x = value; OnPropertyChanged(); } } }
|
||||
public float Y { get => y; set { if (value != y) { y = value; OnPropertyChanged(); } } }
|
||||
public float Radius { get => radius; set { if (value != radius) { radius = value; OnPropertyChanged(); } } }
|
||||
}
|
||||
|
||||
public AIConfigViewModel(NpcInfoHelperInterface npcInfoHelper, ItemInfoHelperInterface itemInfoHelper, SkillInfoHelperInterface skillInfoHelper, Config config, ConfigSerializerInterface configSerializer, ConfigDeserializerInterface configDeserializer)
|
||||
{
|
||||
this.npcInfoHelper = npcInfoHelper;
|
||||
this.itemInfoHelper = itemInfoHelper;
|
||||
this.skillInfoHelper = skillInfoHelper;
|
||||
this.config = config;
|
||||
this.configSerializer = configSerializer;
|
||||
this.configDeserializer = configDeserializer;
|
||||
SaveDialogCommand = new RelayCommand(OnSaveDialog);
|
||||
OpenDialogCommand = new RelayCommand(OnOpenDialog);
|
||||
SaveCommand = new RelayCommand(OnSave);
|
||||
ResetCommand = new RelayCommand(OnReset);
|
||||
GetHeroPosition = new RelayCommand(OnGetHeroPosition);
|
||||
Skills = new ObservableCollection<ObjectInfo>(skillInfoHelper.GetAllSkills().Select(x => x.Value).Where(x => x.IsActive).ToList());
|
||||
}
|
||||
|
||||
public ICommand SaveDialogCommand { get; }
|
||||
public ICommand OpenDialogCommand { get; }
|
||||
public ICommand SaveCommand { get; }
|
||||
public ICommand ResetCommand { get; }
|
||||
public ICommand GetHeroPosition { get; }
|
||||
public Action? Close { get; set; }
|
||||
public Action<string>? OpenSaveDialog { get; set; }
|
||||
public Func<string?>? OpenOpenDialog { get; set; }
|
||||
|
||||
public ObservableCollection<ObjectInfo> Skills { get; set; }
|
||||
|
||||
public uint MobsMaxDeltaZ { get => mobsMaxDeltaZ; set { if (value != mobsMaxDeltaZ) { mobsMaxDeltaZ = value; OnPropertyChanged(); } } }
|
||||
public ObservableCollection<ObjectInfo> ExcludedMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> SelectedExcludedMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> IncludedMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> SelectedIncludedMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public byte? MobLevelLowerLimit { get => mobLevelLowerLimit; set { if (value != mobLevelLowerLimit) { mobLevelLowerLimit = value; OnPropertyChanged(); } } }
|
||||
public byte? MobLevelUpperLimit { get => mobLevelUpperLimit; set { if (value != mobLevelUpperLimit) { mobLevelUpperLimit = value; OnPropertyChanged(); } } }
|
||||
public ObservableCollection<ObjectInfo> ExcludedSpoilMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> SelectedExcludedSpoilMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> IncludedSpoilMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> SelectedIncludedSpoilMobs { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public bool SpoilIfPossible { get => spoilIfPossible; set { if (value != spoilIfPossible) { spoilIfPossible = value; OnPropertyChanged(); } } }
|
||||
public bool SpoilIsPriority { get => spoilIsPriority; set { if (value != spoilIsPriority) { spoilIsPriority = value; OnPropertyChanged(); } } }
|
||||
public uint SpoilSkillId { get => spoilSkillId; set { if (value != spoilSkillId) { spoilSkillId = value; OnPropertyChanged(); } } }
|
||||
public uint SweeperSkillId { get => sweeperSkillId; set { if (value != sweeperSkillId) { sweeperSkillId = value; OnPropertyChanged(); } } }
|
||||
public byte SweepAttemptsCount { get => sweepAttemptsCount; set { if (value != sweepAttemptsCount) { sweepAttemptsCount = value; OnPropertyChanged(); } } }
|
||||
public ObservableCollection<ObjectInfo> ExcludedItems { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> SelectedExcludedItems { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> IncludedItems { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public ObservableCollection<ObjectInfo> SelectedIncludedItems { get; set; } = new ObservableCollection<ObjectInfo>();
|
||||
public bool PickupIfPossible { get => pickupIfPossible; set { if (value != pickupIfPossible) { pickupIfPossible = value; OnPropertyChanged(); } } }
|
||||
public uint PickupMaxDeltaZ { get => pickupMaxDeltaZ; set { if (value != pickupMaxDeltaZ) { pickupMaxDeltaZ = value; OnPropertyChanged(); } } }
|
||||
public byte PickupAttemptsCount { get => pickupAttemptsCount; set { if (value != pickupAttemptsCount) { pickupAttemptsCount = value; OnPropertyChanged(); } } }
|
||||
public byte RestStartPercentHp { get => restStartPercentHp; set { if (value != restStartPercentHp) { restStartPercentHp = value; OnPropertyChanged(); } } }
|
||||
public byte RestEndPercentHp { get => restEndPercentHp; set { if (value != restEndPercentHp) { restEndPercentHp = value; OnPropertyChanged(); } } }
|
||||
public byte RestStartPercentMp { get => restStartPercentMp; set { if (value != restStartPercentMp) { restStartPercentMp = value; OnPropertyChanged(); } } }
|
||||
public byte RestEndPercentMp { get => restEndPercentMp; set { if (value != restEndPercentMp) { restEndPercentMp = value; OnPropertyChanged(); } } }
|
||||
public ObservableCollection<SkillCondition> CombatSkills { get; set; } = new ObservableCollection<SkillCondition>();
|
||||
public bool AutoUseShots { get => autoUseShots; set { if (value != autoUseShots) { autoUseShots = value; OnPropertyChanged(); } } }
|
||||
public uint AttackDistanceMili { get => attackDistanceMili; set { if (value != attackDistanceMili) { attackDistanceMili = value; OnPropertyChanged(); } } }
|
||||
public uint AttackDistanceBow { get => attackDistanceBow; set { if (value != attackDistanceBow) { attackDistanceBow = value; OnPropertyChanged(); } } }
|
||||
public bool UseOnlySkills { get => useOnlySkills; set { if (value != useOnlySkills) { useOnlySkills = value; OnPropertyChanged(); } } }
|
||||
public CombatZone Zone { get => combatZone; set { if (value != combatZone) { combatZone = value; OnPropertyChanged(); } } }
|
||||
public byte DelevelingTargetLevel { get => delevelingTargetLevel; set { if (value != delevelingTargetLevel) { delevelingTargetLevel = value; OnPropertyChanged(); } } }
|
||||
public uint DelevelingAttackDistance { get => delevelingAttackDistance; set { if (value != delevelingAttackDistance) { delevelingAttackDistance = value; OnPropertyChanged(); } } }
|
||||
public uint DelevelingSkillId { get => delevelingSkillId; set { if (value != delevelingSkillId) { delevelingSkillId = value; OnPropertyChanged(); } } }
|
||||
|
||||
public void LoadConfig()
|
||||
{
|
||||
LoadConfigFrom(config);
|
||||
}
|
||||
|
||||
private void LoadConfigFrom(Config config)
|
||||
{
|
||||
LoadCollections(config);
|
||||
MobsMaxDeltaZ = config.Combat.MobsMaxDeltaZ;
|
||||
MobLevelLowerLimit = config.Combat.MobLevelLowerLimit;
|
||||
MobLevelUpperLimit = config.Combat.MobLevelUpperLimit;
|
||||
SpoilIfPossible = config.Combat.SpoilIfPossible;
|
||||
SpoilIsPriority = config.Combat.SpoilIsPriority;
|
||||
SpoilSkillId = config.Combat.SpoilSkillId;
|
||||
SweeperSkillId = config.Combat.SweeperSkillId;
|
||||
SweepAttemptsCount = config.Combat.SweepAttemptsCount;
|
||||
PickupIfPossible = config.Combat.PickupIfPossible;
|
||||
PickupMaxDeltaZ = config.Combat.PickupMaxDeltaZ;
|
||||
PickupAttemptsCount = config.Combat.PickupAttemptsCount;
|
||||
RestStartPercentHp = config.Combat.RestStartPercentHp;
|
||||
RestEndPercentHp = config.Combat.RestEndPecentHp;
|
||||
RestStartPercentMp = config.Combat.RestStartPecentMp;
|
||||
RestEndPercentMp = config.Combat.RestEndPecentMp;
|
||||
AutoUseShots = config.Combat.AutoUseShots;
|
||||
AttackDistanceMili = config.Combat.AttackDistanceMili;
|
||||
AttackDistanceBow = config.Combat.AttackDistanceBow;
|
||||
UseOnlySkills = config.Combat.UseOnlySkills;
|
||||
Zone.X = config.Combat.Zone.Center.X;
|
||||
Zone.Y = config.Combat.Zone.Center.Y;
|
||||
Zone.Radius = config.Combat.Zone.Radius;
|
||||
DelevelingTargetLevel = config.Deleveling.TargetLevel;
|
||||
DelevelingAttackDistance = config.Deleveling.AttackDistance;
|
||||
DelevelingSkillId = config.Deleveling.SkillId;
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
config.Combat.MobsMaxDeltaZ = MobsMaxDeltaZ;
|
||||
config.Combat.MobLevelLowerLimit = MobLevelLowerLimit;
|
||||
config.Combat.MobLevelUpperLimit = MobLevelUpperLimit;
|
||||
config.Combat.SpoilIfPossible = SpoilIfPossible;
|
||||
config.Combat.SpoilIsPriority = SpoilIsPriority;
|
||||
config.Combat.SpoilSkillId = SpoilSkillId;
|
||||
config.Combat.SweeperSkillId = SweeperSkillId;
|
||||
config.Combat.SweepAttemptsCount = SweepAttemptsCount;
|
||||
config.Combat.PickupIfPossible = PickupIfPossible;
|
||||
config.Combat.PickupMaxDeltaZ = PickupMaxDeltaZ;
|
||||
config.Combat.PickupAttemptsCount = PickupAttemptsCount;
|
||||
config.Combat.RestStartPercentHp = RestStartPercentHp;
|
||||
config.Combat.RestEndPecentHp = RestEndPercentHp;
|
||||
config.Combat.RestStartPecentMp = RestStartPercentMp;
|
||||
config.Combat.RestEndPecentMp = RestEndPercentMp;
|
||||
config.Combat.AutoUseShots = AutoUseShots;
|
||||
config.Combat.AttackDistanceMili = AttackDistanceMili;
|
||||
config.Combat.AttackDistanceBow = AttackDistanceBow;
|
||||
config.Combat.UseOnlySkills = UseOnlySkills;
|
||||
config.Combat.Zone.Center.X = Zone.X;
|
||||
config.Combat.Zone.Center.Y = Zone.Y;
|
||||
config.Combat.Zone.Center.Z = 0;
|
||||
config.Combat.Zone.Radius = Zone.Radius;
|
||||
config.Deleveling.TargetLevel = DelevelingTargetLevel;
|
||||
config.Deleveling.AttackDistance = DelevelingAttackDistance;
|
||||
config.Deleveling.SkillId = DelevelingSkillId;
|
||||
SaveCollections();
|
||||
}
|
||||
|
||||
private void LoadCollections(Config config)
|
||||
{
|
||||
ExcludedMobs.Clear();
|
||||
npcInfoHelper.GetAllNpc().ForEach(n => ExcludedMobs.Add(n));
|
||||
SelectedExcludedMobs.Clear();
|
||||
IncludedMobs.Clear();
|
||||
npcInfoHelper.GetAllNpc().ForEach(n => IncludedMobs.Add(n));
|
||||
SelectedIncludedMobs.Clear();
|
||||
ExcludedSpoilMobs.Clear();
|
||||
npcInfoHelper.GetAllNpc().ForEach(n => ExcludedSpoilMobs.Add(n));
|
||||
SelectedExcludedSpoilMobs.Clear();
|
||||
IncludedSpoilMobs.Clear();
|
||||
npcInfoHelper.GetAllNpc().ForEach(n => IncludedSpoilMobs.Add(n));
|
||||
SelectedIncludedSpoilMobs.Clear();
|
||||
ExcludedItems.Clear();
|
||||
itemInfoHelper.GetAllItems().ForEach(n => ExcludedItems.Add(n));
|
||||
SelectedExcludedItems.Clear();
|
||||
IncludedItems.Clear();
|
||||
itemInfoHelper.GetAllItems().ForEach(n => IncludedItems.Add(n));
|
||||
SelectedIncludedItems.Clear();
|
||||
|
||||
var loadCollection = (ObservableCollection<ObjectInfo> items, ObservableCollection<ObjectInfo> selectedItems, Dictionary<uint, bool> configItems) =>
|
||||
{
|
||||
for (var i = items.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var mob = items[i];
|
||||
if (configItems.ContainsKey(mob.Id))
|
||||
{
|
||||
items.Remove(mob);
|
||||
selectedItems.Add(mob);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadCollection(ExcludedMobs, SelectedExcludedMobs, config.Combat.ExcludedMobIds);
|
||||
loadCollection(IncludedMobs, SelectedIncludedMobs, config.Combat.IncludedMobIds);
|
||||
loadCollection(ExcludedSpoilMobs, SelectedExcludedSpoilMobs, config.Combat.ExcludedSpoilMobIds);
|
||||
loadCollection(IncludedSpoilMobs, SelectedIncludedSpoilMobs, config.Combat.IncludedSpoilMobIds);
|
||||
loadCollection(ExcludedItems, SelectedExcludedItems, config.Combat.ExcludedItemIdsToPickup);
|
||||
loadCollection(IncludedItems, SelectedIncludedItems, config.Combat.IncludedItemIdsToPickup);
|
||||
|
||||
CombatSkills.RemoveAll();
|
||||
config.Combat.SkillConditions.ForEach(x =>
|
||||
{
|
||||
CombatSkills.Add(new SkillCondition()
|
||||
{
|
||||
Id = x.Id,
|
||||
MaxTargetPercentHp = x.MaxTargetPercentHp,
|
||||
MinPlayerPercentMp = x.MinPlayerPercentMp,
|
||||
MaxPlayerPercentHp = x.MaxPlayerPercentHp
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void SaveCollections()
|
||||
{
|
||||
config.Combat.ExcludedMobIds = SelectedExcludedMobs.ToDictionary(x => x.Id, x => true);
|
||||
config.Combat.IncludedMobIds = SelectedIncludedMobs.ToDictionary(x => x.Id, x => true);
|
||||
|
||||
config.Combat.ExcludedSpoilMobIds = SelectedExcludedSpoilMobs.ToDictionary(x => x.Id, x => true);
|
||||
config.Combat.IncludedSpoilMobIds = SelectedIncludedSpoilMobs.ToDictionary(x => x.Id, x => true);
|
||||
|
||||
config.Combat.ExcludedItemIdsToPickup = SelectedExcludedItems.ToDictionary(x => x.Id, x => true);
|
||||
config.Combat.IncludedItemIdsToPickup = SelectedIncludedItems.ToDictionary(x => x.Id, x => true);
|
||||
|
||||
config.Combat.SkillConditions = CombatSkills.Select(x => new Config.SkillCondition()
|
||||
{
|
||||
Id = x.Id,
|
||||
MaxTargetPercentHp = x.MaxTargetPercentHp,
|
||||
MinPlayerPercentMp = x.MinPlayerPercentMp,
|
||||
MaxPlayerPercentHp = x.MaxPlayerPercentHp
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private void OnSaveDialog(object? sender)
|
||||
{
|
||||
SaveConfig();
|
||||
if (OpenSaveDialog != null)
|
||||
{
|
||||
OpenSaveDialog(configSerializer.Serialize(config));
|
||||
}
|
||||
if (Close != null)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenDialog(object? sender)
|
||||
{
|
||||
if (OpenOpenDialog != null)
|
||||
{
|
||||
var data = OpenOpenDialog();
|
||||
if (data != null)
|
||||
{
|
||||
var config = configDeserializer.Deserialize(data);
|
||||
if (config != null)
|
||||
{
|
||||
LoadConfigFrom(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSave(object? sender)
|
||||
{
|
||||
SaveConfig();
|
||||
if (Close != null)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReset(object? sender)
|
||||
{
|
||||
LoadConfigFrom(config);
|
||||
}
|
||||
|
||||
private void OnGetHeroPosition(object? sender)
|
||||
{
|
||||
if (hero != null)
|
||||
{
|
||||
Zone.X = hero.Transform.Position.X;
|
||||
Zone.Y = hero.Transform.Position.Y;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly NpcInfoHelperInterface npcInfoHelper;
|
||||
private readonly ItemInfoHelperInterface itemInfoHelper;
|
||||
private readonly SkillInfoHelperInterface skillInfoHelper;
|
||||
private readonly Config config;
|
||||
private readonly ConfigSerializerInterface configSerializer;
|
||||
private readonly ConfigDeserializerInterface configDeserializer;
|
||||
private uint mobsMaxDeltaZ = 0;
|
||||
private byte? mobLevelLowerLimit = null;
|
||||
private byte? mobLevelUpperLimit = null;
|
||||
private bool spoilIfPossible = false;
|
||||
private bool spoilIsPriority = false;
|
||||
private uint spoilSkillId = 0;
|
||||
private uint sweeperSkillId = 0;
|
||||
private byte sweepAttemptsCount = 0;
|
||||
private bool pickupIfPossible = false;
|
||||
private uint pickupMaxDeltaZ = 0;
|
||||
private byte pickupAttemptsCount = 0;
|
||||
private byte restStartPercentHp = 0;
|
||||
private byte restEndPercentHp = 0;
|
||||
private byte restStartPercentMp = 0;
|
||||
private byte restEndPercentMp = 0;
|
||||
private bool autoUseShots = false;
|
||||
private uint attackDistanceMili = 0;
|
||||
private uint attackDistanceBow = 0;
|
||||
private bool useOnlySkills = false;
|
||||
private CombatZone combatZone = new CombatZone();
|
||||
private Hero? hero;
|
||||
private byte delevelingTargetLevel = 0;
|
||||
private uint delevelingAttackDistance = 0;
|
||||
private uint delevelingSkillId = 0;
|
||||
}
|
||||
}
|
@ -17,6 +17,12 @@ namespace Client.Application.ViewModels
|
||||
{
|
||||
public class CreatureMapViewModel : ObservableObject
|
||||
{
|
||||
public void MapUpdated(float scale, float viewportWidth, float viewportHeight)
|
||||
{
|
||||
Scale = scale;
|
||||
VieportSize = new Vector3(viewportWidth, viewportHeight, 0);
|
||||
}
|
||||
|
||||
public uint Id => creature.Id;
|
||||
public string Name => creature.Name;
|
||||
public Vector3 Position => new Vector3(
|
||||
@ -64,6 +70,9 @@ namespace Client.Application.ViewModels
|
||||
public bool IsAggressive => creature.AggroRadius > 0 && !creature.VitalStats.IsDead && creature.IsHostile;
|
||||
public float AggroRadius => creature.AggroRadius / scale;
|
||||
public bool IsAttacker => hero.AttackerIds.Contains(creature.Id);
|
||||
public bool IsDead => creature.VitalStats.IsDead;
|
||||
public bool IsHostile => creature.IsHostile;
|
||||
public bool IsSweepable => creature is NPC && ((NPC) creature).SpoilState == SpoilStateEnum.Sweepable;
|
||||
|
||||
public ICommand MouseLeftClickCommand { get; }
|
||||
public ICommand MouseLeftDoubleClickCommand { get; }
|
||||
@ -109,6 +118,7 @@ namespace Client.Application.ViewModels
|
||||
if (e.PropertyName == "IsDead")
|
||||
{
|
||||
OnPropertyChanged("IsAggressive");
|
||||
OnPropertyChanged("IsDead");
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +160,10 @@ namespace Client.Application.ViewModels
|
||||
{
|
||||
OnPropertyChanged("Name");
|
||||
}
|
||||
if (e.PropertyName == "SpoilState")
|
||||
{
|
||||
OnPropertyChanged("IsSweepable");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CreatureInterface creature;
|
||||
|
@ -16,6 +16,12 @@ namespace Client.Application.ViewModels
|
||||
{
|
||||
public class DropMapViewModel : ObservableObject
|
||||
{
|
||||
public void MapUpdated(float scale, float viewportWidth, float viewportHeight)
|
||||
{
|
||||
Scale = scale;
|
||||
VieportSize = new Vector3(viewportWidth, viewportHeight, 0);
|
||||
}
|
||||
|
||||
public uint Id => drop.Id;
|
||||
public string Name => drop.Name + " (" + drop.Amount + ")";
|
||||
public Vector3 Position => new Vector3(
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Client.Application.Components;
|
||||
using Client.Application.Commands;
|
||||
using Client.Application.Components;
|
||||
using Client.Domain.AI;
|
||||
using Client.Domain.Common;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Events;
|
||||
@ -14,6 +16,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Client.Application.ViewModels
|
||||
@ -32,11 +35,13 @@ namespace Client.Application.ViewModels
|
||||
EventHandlerInterface<ItemCreatedEvent>,
|
||||
EventHandlerInterface<ItemDeletedEvent>
|
||||
{
|
||||
|
||||
public void Handle(HeroCreatedEvent @event)
|
||||
{
|
||||
Hero = new HeroSummaryInfoViewModel(@event.Hero);
|
||||
hero = @event.Hero;
|
||||
Map.Hero = hero;
|
||||
Map.CombatZone = new AICombatZoneMapViewModel(aiConfig.Combat.Zone, hero);
|
||||
AddCreature(hero);
|
||||
OnPropertyChanged("Hero");
|
||||
OnPropertyChanged("Map");
|
||||
@ -51,6 +56,7 @@ namespace Client.Application.ViewModels
|
||||
Hero = null;
|
||||
hero = null;
|
||||
Map.Hero = null;
|
||||
Map.CombatZone = null;
|
||||
OnPropertyChanged("Hero");
|
||||
OnPropertyChanged("Map");
|
||||
}
|
||||
@ -115,13 +121,13 @@ namespace Client.Application.ViewModels
|
||||
{
|
||||
if (hero != null)
|
||||
{
|
||||
if (!@event.Item.IsQuest)
|
||||
if (@event.Item is EtcItem && ((EtcItem) @event.Item).IsQuest)
|
||||
{
|
||||
Items.Add(new ItemListViewModel(worldHandler, @event.Item));
|
||||
QuestItems.Add(new ItemListViewModel(worldHandler, @event.Item));
|
||||
}
|
||||
else
|
||||
{
|
||||
QuestItems.Add(new ItemListViewModel(worldHandler, @event.Item));
|
||||
Items.Add(new ItemListViewModel(worldHandler, @event.Item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,6 +138,14 @@ namespace Client.Application.ViewModels
|
||||
QuestItems.RemoveAll(x => x.Id == @event.Id);
|
||||
}
|
||||
|
||||
public Dictionary<TypeEnum, string> AITypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return Enum.GetValues(typeof(TypeEnum)).Cast<TypeEnum>().ToDictionary(x => x, x => x.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCreature(CreatureInterface creature)
|
||||
{
|
||||
if (hero != null)
|
||||
@ -145,13 +159,36 @@ namespace Client.Application.ViewModels
|
||||
Map.Creatures.RemoveAll(x => x.Id == id);
|
||||
}
|
||||
|
||||
public MainViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover)
|
||||
private void OnToggleAI(object? sender)
|
||||
{
|
||||
ai.Toggle();
|
||||
OnPropertyChanged("AIStatus");
|
||||
}
|
||||
|
||||
private void OnChangeAIType(object? param)
|
||||
{
|
||||
if (!(param is TypeEnum))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ai.Type = (TypeEnum) param;
|
||||
OnPropertyChanged("AIType");
|
||||
}
|
||||
|
||||
public MainViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, AIInterface ai, Config aiConfig)
|
||||
{
|
||||
this.worldHandler = worldHandler;
|
||||
this.pathMover = pathMover;
|
||||
this.ai = ai;
|
||||
this.aiConfig = aiConfig;
|
||||
Map = new MapViewModel(pathMover);
|
||||
ToggleAICommand = new RelayCommand(OnToggleAI);
|
||||
ChangeAITypeCommand = new RelayCommand(OnChangeAIType);
|
||||
}
|
||||
|
||||
public ICommand ToggleAICommand { get; set; }
|
||||
public ICommand ChangeAITypeCommand { get; set; }
|
||||
|
||||
public ObservableCollection<ChatMessageViewModel> ChatMessages { get; } = new ObservableCollection<ChatMessageViewModel>();
|
||||
public ObservableCollection<CreatureListViewModel> Creatures { get; } = new ObservableCollection<CreatureListViewModel>();
|
||||
public ObservableCollection<DropListViewModel> Drops { get; } = new ObservableCollection<DropListViewModel>();
|
||||
@ -161,8 +198,13 @@ namespace Client.Application.ViewModels
|
||||
public ObservableCollection<ItemListViewModel> QuestItems { get; } = new ObservableCollection<ItemListViewModel>();
|
||||
public HeroSummaryInfoViewModel? Hero { get; private set; }
|
||||
public MapViewModel Map { get; private set; }
|
||||
public bool AIStatus => ai.IsEnabled;
|
||||
public TypeEnum AIType => ai.Type;
|
||||
|
||||
public Hero? hero;
|
||||
private readonly WorldHandler worldHandler;
|
||||
private readonly AsyncPathMoverInterface pathMover;
|
||||
private readonly AIInterface ai;
|
||||
private readonly Config aiConfig;
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,25 @@
|
||||
using Client.Domain.Common;
|
||||
using Client.Application.Commands;
|
||||
using Client.Domain.Common;
|
||||
using Client.Domain.DTO;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using Client.Domain.ValueObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Collections.Specialized;
|
||||
using Client.Application.Commands;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Client.Infrastructure.Service;
|
||||
using System.Windows.Data;
|
||||
using Client.Domain.DTO;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Client.Application.ViewModels
|
||||
{
|
||||
public class MapViewModel : ObservableObject
|
||||
{
|
||||
public delegate void MapUpdatedEventHandler(float scale, float viewportWidth, float viewportHeight);
|
||||
private event MapUpdatedEventHandler? MapUpdated;
|
||||
|
||||
public Hero? Hero
|
||||
{
|
||||
get => hero;
|
||||
@ -131,22 +128,10 @@ namespace Client.Application.ViewModels
|
||||
this.blocks[block.Id].Visible = true;
|
||||
}
|
||||
|
||||
foreach (var creature in Creatures)
|
||||
{
|
||||
creature.Scale = scale;
|
||||
creature.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
|
||||
}
|
||||
|
||||
foreach (var drop in Drops)
|
||||
if (MapUpdated != null)
|
||||
{
|
||||
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);
|
||||
MapUpdated(scale, (float)ViewportWidth, (float)ViewportHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -236,7 +221,7 @@ namespace Client.Application.ViewModels
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
Path.Clear();
|
||||
Path.RemoveAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,8 +233,16 @@ namespace Client.Application.ViewModels
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var node = (PathNodeViewModel)item;
|
||||
node.Scale = scale;
|
||||
node.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
|
||||
MapUpdated += node.MapUpdated;
|
||||
node.MapUpdated(scale, (float)ViewportWidth, (float)ViewportHeight);
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
var node = (PathNodeViewModel)item;
|
||||
MapUpdated -= node.MapUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -265,9 +258,17 @@ namespace Client.Application.ViewModels
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var creature = (DropMapViewModel)item;
|
||||
creature.Scale = scale;
|
||||
creature.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
|
||||
var drop = (DropMapViewModel)item;
|
||||
MapUpdated += drop.MapUpdated;
|
||||
drop.MapUpdated(scale, (float)ViewportWidth, (float)ViewportHeight);
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
var drop = (DropMapViewModel)item;
|
||||
MapUpdated -= drop.MapUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,8 +280,16 @@ namespace Client.Application.ViewModels
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var creature = (CreatureMapViewModel)item;
|
||||
creature.Scale = scale;
|
||||
creature.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
|
||||
MapUpdated += creature.MapUpdated;
|
||||
creature.MapUpdated(scale, (float)ViewportWidth, (float)ViewportHeight);
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
var creature = (CreatureMapViewModel)item;
|
||||
MapUpdated -= creature.MapUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,6 +298,31 @@ namespace Client.Application.ViewModels
|
||||
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 AICombatZoneMapViewModel? CombatZone {
|
||||
get
|
||||
{
|
||||
return combatZone;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != combatZone)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
if (combatZone != null)
|
||||
{
|
||||
MapUpdated -= combatZone.MapUpdated;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MapUpdated += value.MapUpdated;
|
||||
}
|
||||
combatZone = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly static float MIN_SCALE = 1;
|
||||
public readonly static float MAX_SCALE = 128;
|
||||
@ -301,5 +335,6 @@ namespace Client.Application.ViewModels
|
||||
private double viewportHeight = 0;
|
||||
private Vector3 mousePosition = new Vector3(0, 0, 0);
|
||||
private object pathCollectionLock = new object();
|
||||
private AICombatZoneMapViewModel? combatZone = null;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,12 @@ namespace Client.Application.ViewModels
|
||||
{
|
||||
public class PathNodeViewModel : ObservableObject
|
||||
{
|
||||
public void MapUpdated(float scale, float viewportWidth, float viewportHeight)
|
||||
{
|
||||
Scale = scale;
|
||||
VieportSize = new Vector3(viewportWidth, viewportHeight, 0);
|
||||
}
|
||||
|
||||
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),
|
||||
|
373
Client/Application/Views/AIConfig.xaml
Normal file
373
Client/Application/Views/AIConfig.xaml
Normal file
@ -0,0 +1,373 @@
|
||||
<Window x:Class="Client.Application.Views.AIConfig"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Client.Application.Views"
|
||||
xmlns:components="clr-namespace:Client.Application.Components"
|
||||
mc:Ignorable="d"
|
||||
IsVisibleChanged="Window_IsVisibleChanged"
|
||||
Closing="Window_Closing"
|
||||
SizeToContent="Height"
|
||||
ResizeMode="NoResize"
|
||||
Title="Combat AI Config" Height="600" Width="800"
|
||||
x:Name="root">
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Menu>
|
||||
<MenuItem Header="File">
|
||||
<MenuItem Header="Open" Command="{Binding OpenDialogCommand}"/>
|
||||
<MenuItem Header="Save" Command="{Binding SaveDialogCommand}"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<TabControl Grid.Row="1" TabStripPlacement="Left">
|
||||
<TabItem>
|
||||
<TabItem.Header>Combat AI</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
<TabControl>
|
||||
<TabItem>
|
||||
<TabItem.Header>Combat</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding AutoUseShots}">Auto use soul and spiritshots</CheckBox>
|
||||
<CheckBox Grid.Row="1" Grid.Column="0" IsChecked="{Binding UseOnlySkills}">Use only skills</CheckBox>
|
||||
<StackPanel Grid.Row="2" Grid.Column="0">
|
||||
<Label>Attack distance for mili weapon:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="AttackDistanceMili" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="3" Grid.Column="0">
|
||||
<Label>Attack distance for bows:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="AttackDistanceBow" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="4" Grid.Column="0">
|
||||
<Label>Skill conditions:</Label>
|
||||
<DataGrid
|
||||
AutoGenerateColumns="False"
|
||||
Height="150"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
ItemsSource="{Binding CombatSkills}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="Skill" Width="300">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox
|
||||
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Skills}"
|
||||
SelectedValuePath="Id"
|
||||
SelectedValue="{Binding Id}"
|
||||
DisplayMemberPath="Name">
|
||||
<ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ComboBox.ItemsPanel>
|
||||
</ComboBox>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="Max target % HP" Width="100" Binding="{Binding MaxTargetPercentHp}" />
|
||||
<DataGridTextColumn Header="Min player % MP" Width="100" Binding="{Binding MinPlayerPercentMp}" />
|
||||
<DataGridTextColumn Header="Max player % HP" Width="100" Binding="{Binding MaxPlayerPercentHp}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="5" Grid.Column="0">
|
||||
<Label>Combat zone:</Label>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"></ColumnDefinition>
|
||||
<ColumnDefinition Width="120"></ColumnDefinition>
|
||||
<ColumnDefinition Width="80"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Label Grid.Row="0" Grid.Column="0">X:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left" Grid.Row="0" Grid.Column="1">
|
||||
<TextBox.Text>
|
||||
<Binding Path="Zone.X" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<Button Grid.Row="0" Grid.Column="2" Command="{Binding GetHeroPosition}">Current</Button>
|
||||
<Label Grid.Row="1" Grid.Column="0">Y:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left" Grid.Row="1" Grid.Column="1">
|
||||
<TextBox.Text>
|
||||
<Binding Path="Zone.Y" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<Label Grid.Row="2" Grid.Column="0">Radius:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="1">
|
||||
<TextBox.Text>
|
||||
<Binding Path="Zone.Radius" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>Mobs</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<Label>Max delta z:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="MobsMaxDeltaZ" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<components:MultipleObjectSelector
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Source="{Binding ExcludedMobs}"
|
||||
Target="{Binding SelectedExcludedMobs}"
|
||||
Header="Excluded:"/>
|
||||
<components:MultipleObjectSelector
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Source="{Binding IncludedMobs}"
|
||||
Target="{Binding SelectedIncludedMobs}"
|
||||
Header="Included:"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<Label>Mobs level range:</Label>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Width="100">
|
||||
<TextBox.Text>
|
||||
<Binding Path="MobLevelLowerLimit" UpdateSourceTrigger="PropertyChanged" TargetNullValue=""/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<TextBlock><= Player level <=</TextBlock>
|
||||
<TextBox Width="100">
|
||||
<TextBox.Text>
|
||||
<Binding Path="MobLevelUpperLimit" UpdateSourceTrigger="PropertyChanged" TargetNullValue=""/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>Drop</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" IsChecked="{Binding PickupIfPossible}">Pickup if possible</CheckBox>
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<Label>Max delta z:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="PickupMaxDeltaZ" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<Label>Pickup attempts count:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="PickupAttemptsCount" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<components:MultipleObjectSelector
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Source="{Binding ExcludedItems}"
|
||||
Target="{Binding SelectedExcludedItems}"
|
||||
Header="Excluded:"/>
|
||||
<components:MultipleObjectSelector
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Source="{Binding IncludedItems}"
|
||||
Target="{Binding SelectedIncludedItems}"
|
||||
Header="Included:"/>
|
||||
</Grid>
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>Spoil</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" IsChecked="{Binding SpoilIfPossible}">Spoil if possible</CheckBox>
|
||||
<CheckBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" IsChecked="{Binding SpoilIsPriority}">Spoil is priority</CheckBox>
|
||||
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<Label>Sweep attempts count:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="SweepAttemptsCount" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<components:MultipleObjectSelector
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Source="{Binding ExcludedSpoilMobs}"
|
||||
Target="{Binding SelectedExcludedSpoilMobs}"
|
||||
Header="Excluded:"/>
|
||||
<components:MultipleObjectSelector
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Source="{Binding IncludedSpoilMobs}"
|
||||
Target="{Binding SelectedIncludedSpoilMobs}"
|
||||
Header="Included:"/>
|
||||
<StackPanel Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<components:ObjectSelector
|
||||
Source="{Binding Skills}"
|
||||
SelectedValue="{Binding SpoilSkillId, Mode=TwoWay}"
|
||||
Header="Spoil skill:"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<components:ObjectSelector
|
||||
Source="{Binding Skills}"
|
||||
SelectedValue="{Binding SweeperSkillId, Mode=TwoWay}"
|
||||
Header="Sweeper skill:"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>Rest</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0">
|
||||
<Label>% HP range:</Label>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Width="100">
|
||||
<TextBox.Text>
|
||||
<Binding Path="RestStartPercentHp" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<TextBlock>-</TextBlock>
|
||||
<TextBox Width="100">
|
||||
<TextBox.Text>
|
||||
<Binding Path="RestEndPercentHp" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1">
|
||||
<Label>% MP range:</Label>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Width="100">
|
||||
<TextBox.Text>
|
||||
<Binding Path="RestStartPercentMp" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<TextBlock>-</TextBlock>
|
||||
<TextBox Width="100">
|
||||
<TextBox.Text>
|
||||
<Binding Path="RestEndPercentMp" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>Deleveling AI</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Grid.Column="0">
|
||||
<Label>Target level:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="DelevelingTargetLevel" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Grid.Column="0">
|
||||
<Label>Attack distance:</Label>
|
||||
<TextBox Width="100" HorizontalAlignment="Left">
|
||||
<TextBox.Text>
|
||||
<Binding Path="DelevelingAttackDistance" UpdateSourceTrigger="PropertyChanged"/>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Grid.Column="0">
|
||||
<components:ObjectSelector
|
||||
Source="{Binding Skills}"
|
||||
SelectedValue="{Binding DelevelingSkillId, Mode=TwoWay}"
|
||||
Header="Attacking skill:"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0,10,10,0">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="{x:Type Button}">
|
||||
<Setter Property="Margin" Value="8,0,0,0"/>
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
<Button Command="{Binding SaveCommand}">Save</Button>
|
||||
<Button Command="{Binding ResetCommand}">Reset</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
83
Client/Application/Views/AIConfig.xaml.cs
Normal file
83
Client/Application/Views/AIConfig.xaml.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using Client.Application.Commands;
|
||||
using Client.Application.ViewModels;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Client.Application.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for CombatAIConfigView.xaml
|
||||
/// </summary>
|
||||
public partial class AIConfig : Window
|
||||
{
|
||||
private AIConfigViewModel viewModel;
|
||||
|
||||
public AIConfig(AIConfigViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.viewModel = viewModel;
|
||||
DataContext = viewModel;
|
||||
if (viewModel.Close == null)
|
||||
{
|
||||
viewModel.Close = () => Close();
|
||||
}
|
||||
if (viewModel.OpenSaveDialog == null)
|
||||
{
|
||||
viewModel.OpenSaveDialog = (string data) =>
|
||||
{
|
||||
var dialog = new SaveFileDialog()
|
||||
{
|
||||
Filter = "Json files(*.json)|*.json"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
File.WriteAllText(dialog.FileName, data);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (viewModel.OpenOpenDialog == null)
|
||||
{
|
||||
viewModel.OpenOpenDialog = () =>
|
||||
{
|
||||
var dialog = new OpenFileDialog()
|
||||
{
|
||||
Filter = "Json files(*.json)|*.json"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
return File.ReadAllText(dialog.FileName);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if ((bool) e.NewValue)
|
||||
{
|
||||
viewModel.LoadConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -29,18 +29,59 @@
|
||||
<ColumnDefinition Width="444"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="22"></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition Height="240"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox x:Name="listBox" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding ChatMessages, Mode=OneWay}">
|
||||
<Menu Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<MenuItem Header="AI options">
|
||||
<MenuItem Command="{Binding ToggleAICommand}">
|
||||
<MenuItem.Style>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="Header" Value="Start"></Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding AIStatus, Mode=OneWay}" Value="True">
|
||||
<Setter Property="Header" Value="Stop"></Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</MenuItem.Style>
|
||||
</MenuItem>
|
||||
<MenuItem ItemsSource="{Binding AITypes}" Header="AI type">
|
||||
<MenuItem.ItemContainerStyle>
|
||||
<Style>
|
||||
<Setter Property="MenuItem.Header" Value="{Binding Value}"/>
|
||||
<Setter Property="MenuItem.Command" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ChangeAITypeCommand}"/>
|
||||
<Setter Property="MenuItem.CommandParameter" Value="{Binding Key}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Value="True">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<services:EqualityConverter />
|
||||
</MultiBinding.Converter>
|
||||
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" Path="DataContext.AIType" />
|
||||
<Binding Path="Key" />
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<Setter Property="MenuItem.IsChecked" Value="True"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</MenuItem.ItemContainerStyle>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Config" Click="AIConfig_Click"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<ListBox x:Name="listBox" Grid.Row="2" Grid.Column="0" ItemsSource="{Binding ChatMessages, Mode=OneWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Text, Mode=OneWay}" Foreground="{Binding Path=Color, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<components:Map DataContext="{Binding Map}" />
|
||||
<TabControl Grid.Row="0" Grid.Column="1">
|
||||
<components:Map Grid.Column="0" Grid.Row="1" DataContext="{Binding Map}" />
|
||||
<TabControl Grid.Row="1" Grid.Column="1">
|
||||
<TabItem>
|
||||
<TabItem.Header>Environment</TabItem.Header>
|
||||
<TabItem.Content>
|
||||
@ -156,7 +197,7 @@
|
||||
</TabItem.Content>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
<Grid Grid.Row="1" Grid.Column="1" DataContext="{Binding Hero, Mode=OneWay}" Margin="4" Visibility="{Binding Path=.,Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<Grid Grid.Row="2" Grid.Column="1" DataContext="{Binding Hero, Mode=OneWay}" Margin="4" Visibility="{Binding Path=.,Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="1*"></ColumnDefinition>
|
||||
@ -224,13 +265,6 @@
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<ListBox Grid.Row="3" Margin="4" ItemsSource="{Binding Attackers, Mode=OneWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel Grid.Column="1" DataContext="{Binding Target, Mode=OneWay}" Visibility="{Binding Path=.,Converter={StaticResource NullToVisibilityConverter}}" Margin="4">
|
||||
<TextBlock FontSize="16" Text="{Binding Path=Name, Mode=OneWay}"></TextBlock>
|
||||
<TextBlock Text="{Binding Path=BriefInfo, Mode=OneWay}"></TextBlock>
|
||||
|
@ -1,7 +1,9 @@
|
||||
using Client.Application.Components;
|
||||
using Client.Application.ViewModels;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@ -23,13 +25,23 @@ namespace Client.Application.Views
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly MainViewModel mainViewModel;
|
||||
private readonly AIConfigViewModel aiConfigViewModel;
|
||||
private AIConfig aiConfigView;
|
||||
|
||||
public MainWindow(MainViewModel mainViewModel)
|
||||
public MainWindow(MainViewModel mainViewModel, AIConfigViewModel aiConfigViewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = mainViewModel;
|
||||
this.mainViewModel = mainViewModel;
|
||||
this.aiConfigViewModel = aiConfigViewModel;
|
||||
aiConfigView = new AIConfig(aiConfigViewModel);
|
||||
}
|
||||
|
||||
private void AIConfig_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
aiConfigView.Owner = this;
|
||||
aiConfigView.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
27648
Client/Assets/data/itemInfo.json
Normal file
27648
Client/Assets/data/itemInfo.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10736
Client/Assets/data/skillInfo.json
Normal file
10736
Client/Assets/data/skillInfo.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Client/Assets/icons/none.png
Normal file
BIN
Client/Assets/icons/none.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Client.Application.ViewModels;
|
||||
using Client.Domain.AI;
|
||||
using Client.Domain.Events;
|
||||
using Client.Domain.Factories;
|
||||
using Client.Domain.Parsers;
|
||||
@ -26,6 +29,7 @@ namespace Client
|
||||
private readonly EventBusInterface eventBus;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly string dllName;
|
||||
private readonly AIInterface ai;
|
||||
|
||||
public Bot(
|
||||
IServiceProvider serviceProvider,
|
||||
@ -36,11 +40,12 @@ namespace Client
|
||||
messageParser = serviceProvider.GetRequiredService<MessageParserInterface>();
|
||||
entityHandlerFactory = serviceProvider.GetRequiredService<EntityHandlerFactoryInterface>();
|
||||
eventBus = serviceProvider.GetRequiredService<EventBusInterface>();
|
||||
ai = serviceProvider.GetRequiredService<AIInterface>();
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.dllName = dllName;
|
||||
}
|
||||
|
||||
public async void StartAsync()
|
||||
public async Task StartAsync()
|
||||
{
|
||||
int hDll = LoadLibrary(dllName);
|
||||
|
||||
@ -56,9 +61,16 @@ namespace Client
|
||||
|
||||
await transport.ConnectAsync();
|
||||
await transport.SendAsync("invalidate");
|
||||
var aiTask = Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await transport.StartReceiveAsync();
|
||||
await ai.Update();
|
||||
}
|
||||
});
|
||||
while (true)
|
||||
{
|
||||
await transport.ReceiveAsync();
|
||||
await transport.ConnectAsync();
|
||||
}
|
||||
}
|
||||
@ -90,9 +102,13 @@ namespace Client
|
||||
eventBus.Subscrbe((EventHandlerInterface<ItemCreatedEvent>)worldHandler);
|
||||
eventBus.Subscrbe((EventHandlerInterface<ItemDeletedEvent>)worldHandler);
|
||||
|
||||
eventBus.Subscrbe((EventHandlerInterface<TargetChangedEvent>)serviceProvider.GetRequiredService<HeroHandler>());
|
||||
eventBus.Subscrbe((EventHandlerInterface<TargetChangedEvent>)serviceProvider.GetRequiredService<NpcHandler>());
|
||||
eventBus.Subscrbe((EventHandlerInterface<TargetChangedEvent>)serviceProvider.GetRequiredService<PlayerHandler>());
|
||||
eventBus.Subscrbe(serviceProvider.GetRequiredService<HeroHandler>());
|
||||
eventBus.Subscrbe(serviceProvider.GetRequiredService<NpcHandler>());
|
||||
eventBus.Subscrbe(serviceProvider.GetRequiredService<PlayerHandler>());
|
||||
|
||||
var configViewModel = serviceProvider.GetRequiredService<AIConfigViewModel>();
|
||||
eventBus.Subscrbe((EventHandlerInterface<HeroCreatedEvent>)configViewModel);
|
||||
eventBus.Subscrbe((EventHandlerInterface<HeroDeletedEvent>)configViewModel);
|
||||
}
|
||||
|
||||
private void OnMessage(string args)
|
||||
|
@ -26,9 +26,16 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\data\itemInfo.json" />
|
||||
<None Remove="Assets\data\skillInfo.json" />
|
||||
<None Remove="Assets\icons\_fallback - Copy.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
102
Client/Domain/AI/AI.cs
Normal file
102
Client/Domain/AI/AI.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using Client.Domain.AI.State;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Events;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Client.Domain.AI
|
||||
{
|
||||
public class AI : AIInterface
|
||||
{
|
||||
public AI(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, TransitionBuilderLocator locator)
|
||||
{
|
||||
this.worldHandler = worldHandler;
|
||||
this.config = config;
|
||||
this.asyncPathMover = asyncPathMover;
|
||||
this.locator = locator;
|
||||
states = StateBuilder.Build(this);
|
||||
ResetState();
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
isEnabled = !isEnabled;
|
||||
if (isEnabled)
|
||||
{
|
||||
ResetState();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled => isEnabled;
|
||||
|
||||
public TypeEnum Type { get { return type; } set { if (type != value) { type = value; ResetState(); } } }
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
await Task.Delay((int) config.DelayBetweenTransitions);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (isEnabled && worldHandler.Hero != null)
|
||||
{
|
||||
states[currentState].Execute();
|
||||
foreach (var transition in locator.Get(Type).Build())
|
||||
{
|
||||
if (transition.fromStates.ContainsKey(BaseState.Type.Any) && transition.toState != currentState || transition.fromStates.ContainsKey(currentState))
|
||||
{
|
||||
if (transition.predicate(worldHandler, config, states[currentState]))
|
||||
{
|
||||
states[currentState].OnLeave();
|
||||
currentState = transition.toState;
|
||||
Debug.WriteLine(currentState.ToString());
|
||||
states[currentState].OnEnter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetState();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public WorldHandler GetWorldHandler()
|
||||
{
|
||||
return worldHandler;
|
||||
}
|
||||
|
||||
public Config GetConfig()
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
public AsyncPathMoverInterface GetAsyncPathMover()
|
||||
{
|
||||
return asyncPathMover;
|
||||
}
|
||||
|
||||
private void ResetState()
|
||||
{
|
||||
currentState = BaseState.Type.Idle;
|
||||
}
|
||||
|
||||
private readonly WorldHandler worldHandler;
|
||||
private readonly Config config;
|
||||
private readonly AsyncPathMoverInterface asyncPathMover;
|
||||
private readonly TransitionBuilderLocator locator;
|
||||
private BaseState.Type currentState;
|
||||
private Dictionary<BaseState.Type, BaseState> states = new Dictionary<BaseState.Type, BaseState>();
|
||||
private bool isEnabled = false;
|
||||
private TypeEnum type = TypeEnum.Combat;
|
||||
}
|
||||
}
|
21
Client/Domain/AI/AIInterface.cs
Normal file
21
Client/Domain/AI/AIInterface.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Client.Domain.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI
|
||||
{
|
||||
public interface AIInterface
|
||||
{
|
||||
Task Update();
|
||||
|
||||
void Toggle();
|
||||
|
||||
bool IsEnabled { get; }
|
||||
|
||||
TypeEnum Type { get; set; }
|
||||
}
|
||||
}
|
25
Client/Domain/AI/Combat/CombatZone.cs
Normal file
25
Client/Domain/AI/Combat/CombatZone.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Client.Domain.Common;
|
||||
using Client.Domain.ValueObjects;
|
||||
|
||||
namespace Client.Domain.AI.Combat
|
||||
{
|
||||
public class CombatZone : ObservableObject
|
||||
{
|
||||
public CombatZone(Vector3 center, float radius)
|
||||
{
|
||||
Center = center;
|
||||
Radius = radius;
|
||||
}
|
||||
|
||||
public bool IsInside(Vector3 point)
|
||||
{
|
||||
return Center.HorizontalDistance(point) <= Radius;
|
||||
}
|
||||
|
||||
public Vector3 Center { get { return center; } set { if (center != value) { center = value; OnPropertyChanged(); } }}
|
||||
public float Radius { get { return radius; } set { if (radius != value) { radius = value; OnPropertyChanged(); } }}
|
||||
|
||||
private float radius;
|
||||
private Vector3 center = new Vector3(0, 0, 0);
|
||||
}
|
||||
}
|
111
Client/Domain/AI/Combat/Helper.cs
Normal file
111
Client/Domain/AI/Combat/Helper.cs
Normal file
@ -0,0 +1,111 @@
|
||||
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.Domain.AI.Combat
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
public static Skill? GetSkillByConfig(WorldHandler worldHandler, Config config, Hero hero, CreatureInterface target)
|
||||
{
|
||||
var conditions = config.Combat.SkillConditions;
|
||||
var targetHp = target.VitalStats.HpPercent;
|
||||
var heroMp = hero.VitalStats.MpPercent;
|
||||
var heroHp = hero.VitalStats.HpPercent;
|
||||
|
||||
foreach (var condition in conditions )
|
||||
{
|
||||
var skill = worldHandler.GetSkillById(condition.Id);
|
||||
if (skill != null)
|
||||
{
|
||||
if (condition.MaxTargetPercentHp < targetHp || condition.MinPlayerPercentMp > heroMp || condition.MaxPlayerPercentHp < heroHp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (skill.IsReadyToUse && hero.VitalStats.Mp >= skill.Cost)
|
||||
{
|
||||
return skill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<Drop> GetDropByConfig(WorldHandler worldHandler, Config config)
|
||||
{
|
||||
if (!config.Combat.PickupIfPossible)
|
||||
{
|
||||
return new List<Drop>();
|
||||
}
|
||||
|
||||
var result = worldHandler.GetDropsSortedByDistanceToHero(config.Combat.PickupMaxDeltaZ)
|
||||
.Where(x => !config.Combat.ExcludedItemIdsToPickup.ContainsKey(x.ItemId));
|
||||
|
||||
if (config.Combat.IncludedItemIdsToPickup.Count > 0)
|
||||
{
|
||||
result = result.Where(x => config.Combat.IncludedItemIdsToPickup.ContainsKey(x.ItemId));
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
public static List<NPC> GetMobsToAttackByConfig(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
var result = worldHandler.GetAliveMobsSortedByDistanceToHero(config.Combat.MobsMaxDeltaZ)
|
||||
.Where(x => !config.Combat.ExcludedMobIds.ContainsKey(x.NpcId));
|
||||
|
||||
result = result.Where(x => config.Combat.Zone.IsInside(x.Transform.Position));
|
||||
|
||||
if (config.Combat.IncludedMobIds.Count > 0)
|
||||
{
|
||||
result = result.Where(x => config.Combat.IncludedMobIds.ContainsKey(x.NpcId));
|
||||
}
|
||||
|
||||
if (config.Combat.MobLevelLowerLimit != null)
|
||||
{
|
||||
result = result.Where(x => (int) (hero.ExperienceInfo.Level - x.Level) <= config.Combat.MobLevelLowerLimit);
|
||||
}
|
||||
|
||||
if (config.Combat.MobLevelUpperLimit != null)
|
||||
{
|
||||
result = result.Where(x => (int) (x.Level - hero.ExperienceInfo.Level) <= config.Combat.MobLevelUpperLimit);
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
public static bool IsOnSpot(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
if (config.Combat.Zone == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var spot = new Vector3(config.Combat.Zone.Center.X, config.Combat.Zone.Center.Y, hero.Transform.Position.Z);
|
||||
return spot.Distance(hero.Transform.Position) <= 200;
|
||||
}
|
||||
|
||||
public static uint GetAttackDistanceByConfig(WorldHandler worldHandler, Config config, Hero hero, CreatureInterface target)
|
||||
{
|
||||
Skill? skill = GetSkillByConfig(worldHandler, config, hero, target);
|
||||
|
||||
var equippedWeapon = worldHandler.GetEquippedWeapon();
|
||||
var expectedDistance = equippedWeapon != null && equippedWeapon.WeaponType == Enums.WeaponTypeEnum.Bow
|
||||
? config.Combat.AttackDistanceBow
|
||||
: config.Combat.AttackDistanceMili;
|
||||
|
||||
if (skill != null)
|
||||
{
|
||||
expectedDistance = (uint)skill.Range;
|
||||
}
|
||||
|
||||
return expectedDistance;
|
||||
}
|
||||
}
|
||||
}
|
147
Client/Domain/AI/Combat/TransitionBuilder.cs
Normal file
147
Client/Domain/AI/Combat/TransitionBuilder.cs
Normal file
@ -0,0 +1,147 @@
|
||||
using Client.Domain.AI.State;
|
||||
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.Domain.AI.Combat
|
||||
{
|
||||
public class TransitionBuilder : TransitionBuilderInterface
|
||||
{
|
||||
public List<TransitionBuilderInterface.Transition> Build()
|
||||
{
|
||||
if (transitions.Count == 0)
|
||||
{
|
||||
transitions = new List<TransitionBuilderInterface.Transition>()
|
||||
{
|
||||
new(new List<BaseState.Type>{BaseState.Type.Any}, BaseState.Type.Dead, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return worldHandler.Hero.VitalStats.IsDead;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Dead}, BaseState.Type.Idle, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return !worldHandler.Hero.VitalStats.IsDead;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Idle, BaseState.Type.MoveToTarget, BaseState.Type.Rest, BaseState.Type.MoveToSpot}, BaseState.Type.FindTarget, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO если нет цели, а тебя атаковали, то моб берется автоматом в таргет, из-за этого баг в Rest и MoveToSpot
|
||||
// а без этой проверки зацикливается MoveToTarget->FindTarget->MoveToTarget
|
||||
// один из вариантов решения, брать себя в таргет при входе в Rest и MoveToSpot
|
||||
if (worldHandler.Hero.Target != null && (worldHandler.Hero.AttackerIds.Contains(worldHandler.Hero.Target.Id) || worldHandler.Hero.Target.VitalStats.IsDead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return worldHandler.Hero.AttackerIds.Count > 0;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.FindTarget}, BaseState.Type.MoveToTarget, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return worldHandler.Hero.HasValidTarget;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.FindTarget}, BaseState.Type.MoveToSpot, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Helper.GetMobsToAttackByConfig(worldHandler, config, worldHandler.Hero).Count == 0
|
||||
&& !Helper.IsOnSpot(worldHandler, config, worldHandler.Hero);
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.MoveToSpot}, BaseState.Type.Idle, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
if (Helper.GetMobsToAttackByConfig(worldHandler, config, worldHandler.Hero).Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Helper.IsOnSpot(worldHandler, config, worldHandler.Hero);
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.MoveToTarget}, BaseState.Type.Idle, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return !worldHandler.Hero.HasValidTarget;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Idle}, BaseState.Type.Rest, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
};
|
||||
return worldHandler.Hero.AttackerIds.Count == 0 && (worldHandler.Hero.VitalStats.HpPercent < config.Combat.RestStartPercentHp
|
||||
|| worldHandler.Hero.VitalStats.MpPercent < config.Combat.RestStartPecentMp);
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Rest}, BaseState.Type.Idle, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return worldHandler.Hero.VitalStats.HpPercent >= config.Combat.RestEndPecentHp
|
||||
&& worldHandler.Hero.VitalStats.MpPercent >= config.Combat.RestEndPecentMp;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.MoveToTarget}, BaseState.Type.Attack, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
if (worldHandler.Hero.Target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.Combat.SpoilIsPriority) {
|
||||
var spoil = worldHandler.GetSkillById(config.Combat.SpoilSkillId);
|
||||
if (spoil != null && !spoil.IsReadyToUse) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var distance = worldHandler.Hero.Transform.Position.HorizontalDistance(worldHandler.Hero.Target.Transform.Position);
|
||||
return distance < Helper.GetAttackDistanceByConfig(worldHandler, config, worldHandler.Hero, worldHandler.Hero.Target);
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Attack}, BaseState.Type.Pickup, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !worldHandler.Hero.HasValidTarget;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Attack}, BaseState.Type.FindTarget, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return worldHandler.Hero.HasValidTarget && worldHandler.Hero.AttackerIds.Count > 0 && !worldHandler.Hero.AttackerIds.Contains(worldHandler.Hero.TargetId);
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Pickup}, BaseState.Type.Idle, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
var currentState = (PickupState) state;
|
||||
|
||||
if (worldHandler.GetSkillById(config.Combat.SweeperSkillId) != null && currentState.IsSweeperMustBeUsed(worldHandler, config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentState.GetDrops(worldHandler, config).Count == 0;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Idle}, BaseState.Type.FindTarget),
|
||||
};
|
||||
}
|
||||
|
||||
return transitions;
|
||||
}
|
||||
|
||||
private List<TransitionBuilderInterface.Transition> transitions = new List<TransitionBuilderInterface.Transition>();
|
||||
}
|
||||
}
|
65
Client/Domain/AI/Config.cs
Normal file
65
Client/Domain/AI/Config.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using Client.Domain.AI.Combat;
|
||||
using Client.Domain.ValueObjects;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Client.Domain.AI
|
||||
{
|
||||
public class Config
|
||||
{
|
||||
public struct SkillCondition
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public byte MaxTargetPercentHp { get; set; }
|
||||
public byte MinPlayerPercentMp { get; set; }
|
||||
public byte MaxPlayerPercentHp { get; set; }
|
||||
}
|
||||
|
||||
public class CombatSection
|
||||
{
|
||||
public uint MobsMaxDeltaZ { get; set; } = 500;
|
||||
public Dictionary<uint, bool> ExcludedMobIds { get; set; } = new Dictionary<uint, bool>();
|
||||
public Dictionary<uint, bool> IncludedMobIds { get; set; } = new Dictionary<uint, bool>();
|
||||
public byte? MobLevelLowerLimit { get; set; } = null;
|
||||
public byte? MobLevelUpperLimit { get; set; } = null;
|
||||
|
||||
public byte RestStartPercentHp { get; set; } = 30;
|
||||
public byte RestEndPecentHp { get; set; } = 100;
|
||||
public byte RestStartPecentMp { get; set; } = 10;
|
||||
public byte RestEndPecentMp { get; set; } = 100;
|
||||
|
||||
public CombatZone Zone { get; set; } = new CombatZone(new Vector3(0, 0, 0), 0);
|
||||
public bool AutoUseShots { get; set; } = true;
|
||||
public uint AttackDistanceMili { get; set; } = 80;
|
||||
public uint AttackDistanceBow { get; set; } = 500;
|
||||
public bool UseOnlySkills { get; set; } = false;
|
||||
public List<SkillCondition> SkillConditions { get; set; } = new List<SkillCondition>();
|
||||
|
||||
public bool SpoilIfPossible { get; set; } = true;
|
||||
public bool SpoilIsPriority { get; set; } = false;
|
||||
public Dictionary<uint, bool> ExcludedSpoilMobIds { get; set; } = new Dictionary<uint, bool>();
|
||||
public Dictionary<uint, bool> IncludedSpoilMobIds { get; set; } = new Dictionary<uint, bool>();
|
||||
public uint SpoilSkillId { get; set; } = 254;
|
||||
public uint SweeperSkillId { get; set; } = 42;
|
||||
public byte SweepAttemptsCount { get; set; } = 10;
|
||||
|
||||
public bool PickupIfPossible { get; set; } = true;
|
||||
public uint PickupMaxDeltaZ { get; set; } = 500;
|
||||
public byte PickupAttemptsCount { get; set; } = 10;
|
||||
public Dictionary<uint, bool> ExcludedItemIdsToPickup { get; set; } = new Dictionary<uint, bool>();
|
||||
public Dictionary<uint, bool> IncludedItemIdsToPickup { get; set; } = new Dictionary<uint, bool>();
|
||||
}
|
||||
|
||||
public class DelevelingSection
|
||||
{
|
||||
public byte TargetLevel { get; set; } = 20;
|
||||
public uint AttackDistance { get; set; } = 80;
|
||||
public uint SkillId { get; set; } = 0;
|
||||
}
|
||||
|
||||
public uint DelayBetweenTransitions { get; set; } = 250;
|
||||
|
||||
public CombatSection Combat { get; } = new CombatSection();
|
||||
|
||||
public DelevelingSection Deleveling { get; } = new DelevelingSection();
|
||||
}
|
||||
}
|
86
Client/Domain/AI/Deleveling/TransitionBuilder.cs
Normal file
86
Client/Domain/AI/Deleveling/TransitionBuilder.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using Client.Domain.AI.State;
|
||||
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.Domain.AI.Deleveling
|
||||
{
|
||||
public class TransitionBuilder : TransitionBuilderInterface
|
||||
{
|
||||
public List<TransitionBuilderInterface.Transition> Build()
|
||||
{
|
||||
if (transitions.Count == 0)
|
||||
{
|
||||
transitions = new List<TransitionBuilderInterface.Transition>()
|
||||
{
|
||||
new(new List<BaseState.Type>{BaseState.Type.Any}, BaseState.Type.Dead, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return worldHandler.Hero.VitalStats.IsDead;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Dead}, BaseState.Type.Idle, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return !worldHandler.Hero.VitalStats.IsDead;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.FindGuard}, BaseState.Type.MoveToTarget, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return worldHandler.Hero.Target != null;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.MoveToTarget}, BaseState.Type.Idle, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
return worldHandler.Hero.Target == null;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.MoveToTarget}, BaseState.Type.AttackGuard, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
if (worldHandler.Hero.Target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var distance = worldHandler.Hero.Transform.Position.HorizontalDistance(worldHandler.Hero.Target.Transform.Position);
|
||||
var expectedDistance = config.Deleveling.AttackDistance;
|
||||
return distance < expectedDistance;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.AttackGuard}, BaseState.Type.FindGuard, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
if (worldHandler.Hero.Target == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var distance = worldHandler.Hero.Transform.Position.HorizontalDistance(worldHandler.Hero.Target.Transform.Position);
|
||||
var expectedDistance = config.Deleveling.AttackDistance;
|
||||
return distance >= expectedDistance;
|
||||
}),
|
||||
new(new List<BaseState.Type>{BaseState.Type.Idle}, BaseState.Type.FindGuard, (worldHandler, config, state) => {
|
||||
if (worldHandler.Hero == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return worldHandler.Hero.ExperienceInfo.Level > config.Deleveling.TargetLevel;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return transitions;
|
||||
}
|
||||
|
||||
private List<TransitionBuilderInterface.Transition> transitions = new List<TransitionBuilderInterface.Transition>();
|
||||
}
|
||||
}
|
13
Client/Domain/AI/IO/ConfigDeserializerInterface.cs
Normal file
13
Client/Domain/AI/IO/ConfigDeserializerInterface.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.IO
|
||||
{
|
||||
public interface ConfigDeserializerInterface
|
||||
{
|
||||
Config? Deserialize(string data);
|
||||
}
|
||||
}
|
13
Client/Domain/AI/IO/ConfigSerializerInterface.cs
Normal file
13
Client/Domain/AI/IO/ConfigSerializerInterface.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.IO
|
||||
{
|
||||
public interface ConfigSerializerInterface
|
||||
{
|
||||
string Serialize(Config config);
|
||||
}
|
||||
}
|
15
Client/Domain/AI/State/AnyState.cs
Normal file
15
Client/Domain/AI/State/AnyState.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class AnyState : BaseState
|
||||
{
|
||||
public AnyState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
28
Client/Domain/AI/State/AttackGuardState.cs
Normal file
28
Client/Domain/AI/State/AttackGuardState.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Client.Application.Components;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class AttackGuardState : BaseState
|
||||
{
|
||||
public AttackGuardState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
if (hero.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var skill = worldHandler.GetSkillById(config.Deleveling.SkillId);
|
||||
|
||||
if (skill != null && skill.IsReadyToUse && skill.Cost <= hero.VitalStats.Mp)
|
||||
{
|
||||
worldHandler.RequestUseSkill(skill.Id, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Client/Domain/AI/State/AttackState.cs
Normal file
72
Client/Domain/AI/State/AttackState.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using Client.Domain.AI.Combat;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class AttackState : BaseState
|
||||
{
|
||||
public AttackState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
if (hero.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.Combat.UseOnlySkills)
|
||||
{
|
||||
worldHandler.RequestAttackOrFollow(hero.Target.Id);
|
||||
}
|
||||
|
||||
if (config.Combat.SpoilIfPossible)
|
||||
{
|
||||
NPC? npc = hero.Target as NPC;
|
||||
var spoil = worldHandler.GetSkillById(config.Combat.SpoilSkillId);
|
||||
if (spoil != null && npc != null && npc.SpoilState == Enums.SpoilStateEnum.None)
|
||||
{
|
||||
var excluded = config.Combat.ExcludedSpoilMobIds;
|
||||
var included = config.Combat.IncludedSpoilMobIds;
|
||||
if (!excluded.ContainsKey(npc.NpcId) && (included.Count == 0 || included.ContainsKey(npc.NpcId)))
|
||||
{
|
||||
if (spoil.IsReadyToUse && hero.VitalStats.Mp >= spoil.Cost)
|
||||
{
|
||||
worldHandler.RequestUseSkill(spoil.Id, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var skill = Helper.GetSkillByConfig(worldHandler, config, hero, hero.Target);
|
||||
if (skill != null)
|
||||
{
|
||||
worldHandler.RequestUseSkill(skill.Id, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
if (config.Combat.AutoUseShots)
|
||||
{
|
||||
// todo use only appropriate grade
|
||||
foreach (var item in worldHandler.GetShotItems())
|
||||
{
|
||||
if (!item.IsAutoused)
|
||||
{
|
||||
worldHandler.RequestToggleAutouseSoulshot(item.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
85
Client/Domain/AI/State/BaseState.cs
Normal file
85
Client/Domain/AI/State/BaseState.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public abstract class BaseState
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Any,
|
||||
Attack,
|
||||
Dead,
|
||||
FindTarget,
|
||||
Idle,
|
||||
MoveToTarget,
|
||||
Pickup,
|
||||
Rest,
|
||||
MoveToSpot,
|
||||
AttackGuard,
|
||||
FindGuard
|
||||
}
|
||||
|
||||
public BaseState(AI ai)
|
||||
{
|
||||
this.ai = ai;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
var hero = ai.GetWorldHandler().Hero;
|
||||
if (hero == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DoExecute(ai.GetWorldHandler(), ai.GetConfig(), ai.GetAsyncPathMover(), hero);
|
||||
}
|
||||
|
||||
public void OnEnter()
|
||||
{
|
||||
var hero = ai.GetWorldHandler().Hero;
|
||||
if (hero == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DoOnEnter(ai.GetWorldHandler(), ai.GetConfig(), hero);
|
||||
}
|
||||
|
||||
public void OnLeave()
|
||||
{
|
||||
var hero = ai.GetWorldHandler().Hero;
|
||||
if (hero == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ai.GetAsyncPathMover().Unlock();
|
||||
DoOnLeave(ai.GetWorldHandler(), ai.GetConfig(), hero);
|
||||
}
|
||||
|
||||
protected virtual void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void DoOnLeave(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private readonly AI ai;
|
||||
}
|
||||
}
|
22
Client/Domain/AI/State/DeadState.cs
Normal file
22
Client/Domain/AI/State/DeadState.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class DeadState : BaseState
|
||||
{
|
||||
public DeadState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
worldHandler.RequestRestartPoint(Enums.RestartPointTypeEnum.Village);
|
||||
}
|
||||
}
|
||||
}
|
28
Client/Domain/AI/State/FindGuardState.cs
Normal file
28
Client/Domain/AI/State/FindGuardState.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class FindGuardState : BaseState
|
||||
{
|
||||
public FindGuardState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
var targetId = worldHandler.GetGuards().FirstOrDefault()?.Id;
|
||||
|
||||
if (targetId != null)
|
||||
{
|
||||
worldHandler.RequestAcquireTarget((uint)targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
Client/Domain/AI/State/FindTargetState.cs
Normal file
34
Client/Domain/AI/State/FindTargetState.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Client.Domain.AI.Combat;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class FindTargetState : BaseState
|
||||
{
|
||||
public FindTargetState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
uint? targetId = hero.AttackerIds.Count > 0 ? hero.AttackerIds.First() : null;
|
||||
|
||||
if (targetId == null)
|
||||
{
|
||||
targetId = Helper.GetMobsToAttackByConfig(worldHandler, config, hero).FirstOrDefault()?.Id;
|
||||
}
|
||||
|
||||
if (targetId != null)
|
||||
{
|
||||
worldHandler.RequestAcquireTarget((uint)targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
Client/Domain/AI/State/IdleState.cs
Normal file
17
Client/Domain/AI/State/IdleState.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class IdleState : BaseState
|
||||
{
|
||||
public IdleState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
37
Client/Domain/AI/State/MoveToSpotState.cs
Normal file
37
Client/Domain/AI/State/MoveToSpotState.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class MoveToSpotState : BaseState
|
||||
{
|
||||
public MoveToSpotState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
worldHandler.RequestAcquireTarget(hero.Id);
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
if (asyncPathMover.IsLocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
asyncPathMover.MoveAsync(new ValueObjects.Vector3(
|
||||
config.Combat.Zone.Center.X,
|
||||
config.Combat.Zone.Center.Y,
|
||||
hero.Transform.Position.Z
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
34
Client/Domain/AI/State/MoveToTargetState.cs
Normal file
34
Client/Domain/AI/State/MoveToTargetState.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Client.Domain.AI.Combat;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using Client.Infrastructure.Service;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class MoveToTargetState : BaseState
|
||||
{
|
||||
public MoveToTargetState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
var target = hero.Target;
|
||||
if (target == null)
|
||||
{
|
||||
target = hero;
|
||||
}
|
||||
|
||||
var distance = hero.Transform.Position.HorizontalDistance(target.Transform.Position);
|
||||
|
||||
if (asyncPathMover.IsLocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (distance >= Helper.GetAttackDistanceByConfig(worldHandler, config, hero, target))
|
||||
{
|
||||
asyncPathMover.MoveAsync(target.Transform.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
Client/Domain/AI/State/PickupState.cs
Normal file
87
Client/Domain/AI/State/PickupState.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using Client.Domain.AI.Combat;
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class PickupState : BaseState
|
||||
{
|
||||
public PickupState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
public List<Drop> GetDrops(WorldHandler worldHandler, Config config)
|
||||
{
|
||||
var drops = Helper.GetDropByConfig(worldHandler, config);
|
||||
for (var i = drops.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (pickupAttempts.ContainsKey(drops[0].Id) && pickupAttempts[drops[0].Id] > config.Combat.PickupAttemptsCount)
|
||||
{
|
||||
drops.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
return drops;
|
||||
}
|
||||
|
||||
public bool IsSweeperMustBeUsed(WorldHandler worldHandler, Config config)
|
||||
{
|
||||
return GetSweepableMobs(worldHandler, config).Count > 0;
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
if (IsSweeperMustBeUsed(worldHandler, config))
|
||||
{
|
||||
var mob = GetSweepableMobs(worldHandler, config).First();
|
||||
var sweeper = worldHandler.GetSkillById(config.Combat.SweeperSkillId);
|
||||
if (sweeper != null && sweeper.IsReadyToUse && hero.VitalStats.Mp >= sweeper.Cost)
|
||||
{
|
||||
worldHandler.RequestAcquireTarget(mob.Id);
|
||||
worldHandler.RequestUseSkill(sweeper.Id, false, false);
|
||||
if (!sweepAttempts.ContainsKey(mob.Id))
|
||||
{
|
||||
sweepAttempts[mob.Id] = 0;
|
||||
}
|
||||
sweepAttempts[mob.Id]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hero.Transform.IsMoving)
|
||||
{
|
||||
var drops = GetDrops(worldHandler, config);
|
||||
if (drops.Count > 0)
|
||||
{
|
||||
worldHandler.RequestPickUp(drops[0].Id);
|
||||
if (!pickupAttempts.ContainsKey(drops[0].Id))
|
||||
{
|
||||
pickupAttempts[drops[0].Id] = 0;
|
||||
}
|
||||
pickupAttempts[drops[0].Id]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DoOnLeave(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
pickupAttempts.Clear();
|
||||
sweepAttempts.Clear();
|
||||
}
|
||||
|
||||
private List<NPC> GetSweepableMobs(WorldHandler worldHandler, Config config)
|
||||
{
|
||||
return worldHandler.GetDeadMobsSortedByDistanceToHero(config.Combat.MobsMaxDeltaZ)
|
||||
.Where(x =>
|
||||
{
|
||||
return x.SpoilState == Enums.SpoilStateEnum.Sweepable &&
|
||||
(!sweepAttempts.ContainsKey(x.Id) || sweepAttempts[x.Id] <= config.Combat.SweepAttemptsCount);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private Dictionary<uint, short> pickupAttempts = new Dictionary<uint, short>();
|
||||
private Dictionary<uint, short> sweepAttempts = new Dictionary<uint, short>();
|
||||
}
|
||||
}
|
42
Client/Domain/AI/State/RestState.cs
Normal file
42
Client/Domain/AI/State/RestState.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Client.Domain.Entities;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI.State
|
||||
{
|
||||
public class RestState : BaseState
|
||||
{
|
||||
public RestState(AI ai) : base(ai)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
worldHandler.RequestAcquireTarget(hero.Id);
|
||||
}
|
||||
|
||||
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
|
||||
{
|
||||
if (!hero.IsStanding)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
worldHandler.RequestSit();
|
||||
}
|
||||
|
||||
protected override void DoOnLeave(WorldHandler worldHandler, Config config, Hero hero)
|
||||
{
|
||||
if (hero.IsStanding)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
worldHandler.RequestStand();
|
||||
}
|
||||
}
|
||||
}
|
30
Client/Domain/AI/StateBuilder.cs
Normal file
30
Client/Domain/AI/StateBuilder.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Client.Domain.AI.State;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI
|
||||
{
|
||||
public static class StateBuilder
|
||||
{
|
||||
public static Dictionary<BaseState.Type, BaseState> Build(AI ai)
|
||||
{
|
||||
return new Dictionary<BaseState.Type, BaseState>
|
||||
{
|
||||
{ BaseState.Type.Any, new AnyState(ai) },
|
||||
{ BaseState.Type.Attack, new AttackState(ai) },
|
||||
{ BaseState.Type.Dead, new DeadState(ai) },
|
||||
{ BaseState.Type.FindTarget, new FindTargetState(ai) },
|
||||
{ BaseState.Type.Idle, new IdleState(ai) },
|
||||
{ BaseState.Type.MoveToTarget, new MoveToTargetState(ai) },
|
||||
{ BaseState.Type.Pickup, new PickupState(ai) },
|
||||
{ BaseState.Type.Rest, new RestState(ai) },
|
||||
{ BaseState.Type.MoveToSpot, new MoveToSpotState(ai) },
|
||||
{ BaseState.Type.AttackGuard, new AttackGuardState(ai) },
|
||||
{ BaseState.Type.FindGuard, new FindGuardState(ai) }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
29
Client/Domain/AI/TransitionBuilderInterface.cs
Normal file
29
Client/Domain/AI/TransitionBuilderInterface.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Client.Domain.AI.State;
|
||||
using Client.Domain.Service;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI
|
||||
{
|
||||
public interface TransitionBuilderInterface
|
||||
{
|
||||
public struct Transition
|
||||
{
|
||||
public readonly Dictionary<BaseState.Type, BaseState.Type> fromStates;
|
||||
public readonly BaseState.Type toState;
|
||||
public readonly Func<WorldHandler, Config, BaseState, bool> predicate;
|
||||
|
||||
public Transition(List<BaseState.Type> fromStates, BaseState.Type toState, Func<WorldHandler, Config, BaseState, bool>? predicate = null)
|
||||
{
|
||||
this.fromStates = fromStates.ToDictionary(x => x, x => x);
|
||||
this.toState = toState;
|
||||
this.predicate = predicate != null ? predicate : (worldHandler, config, state) => { return true; };
|
||||
}
|
||||
}
|
||||
|
||||
List<Transition> Build();
|
||||
}
|
||||
}
|
29
Client/Domain/AI/TransitionBuilderLocator.cs
Normal file
29
Client/Domain/AI/TransitionBuilderLocator.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using CombatTransitionBuilder = Client.Domain.AI.Combat.TransitionBuilder;
|
||||
using DelevelingTransitionBuilder = Client.Domain.AI.Deleveling.TransitionBuilder;
|
||||
|
||||
namespace Client.Domain.AI
|
||||
{
|
||||
public class TransitionBuilderLocator
|
||||
{
|
||||
public TransitionBuilderInterface Get(TypeEnum type)
|
||||
{
|
||||
if (!builders.ContainsKey(type))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TypeEnum.Combat:
|
||||
builders.Add(type, new CombatTransitionBuilder());
|
||||
break;
|
||||
case TypeEnum.Deleveling:
|
||||
builders.Add(type, new DelevelingTransitionBuilder());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builders[type];
|
||||
}
|
||||
|
||||
private Dictionary<TypeEnum, TransitionBuilderInterface> builders = new Dictionary<TypeEnum, TransitionBuilderInterface>();
|
||||
}
|
||||
}
|
14
Client/Domain/AI/TypeEnum.cs
Normal file
14
Client/Domain/AI/TypeEnum.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.AI
|
||||
{
|
||||
public enum TypeEnum: byte
|
||||
{
|
||||
Combat,
|
||||
Deleveling
|
||||
}
|
||||
}
|
@ -10,11 +10,11 @@ namespace Client.Domain.Common
|
||||
public static class ObservableCollectionExtensions
|
||||
{
|
||||
public static void RemoveAll<T>(this ObservableCollection<T> collection,
|
||||
Func<T, bool> condition)
|
||||
Func<T, bool>? condition = null)
|
||||
{
|
||||
for (int i = collection.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (condition(collection[i]))
|
||||
if (condition == null || condition(collection[i]))
|
||||
{
|
||||
collection.RemoveAt(i);
|
||||
}
|
||||
|
@ -15,8 +15,9 @@ namespace Client.Domain.Entities
|
||||
public string Name { get; set; }
|
||||
public string IconName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public int Mana { get { return mana; } set { mana = value; }}
|
||||
public int Mana { get; set; }
|
||||
public uint Weight { get; set; }
|
||||
public virtual string FullDescription => Description;
|
||||
|
||||
public BaseItem(uint id, uint itemId, ItemTypeEnum type, string name, string iconName, string description, int mana, uint weight)
|
||||
{
|
||||
@ -26,10 +27,8 @@ namespace Client.Domain.Entities
|
||||
Name = name;
|
||||
IconName = iconName;
|
||||
Description = description;
|
||||
this.mana = mana;
|
||||
Mana = mana;
|
||||
Weight = weight;
|
||||
}
|
||||
|
||||
private int mana;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ namespace Client.Domain.Entities
|
||||
public uint Amount { get => amount; set => amount = value; }
|
||||
public bool IsQuest { get; set; }
|
||||
public bool IsAutoused { get => isAutoused; set => isAutoused = value; }
|
||||
public string FullDescription { get => Description; }
|
||||
|
||||
public EtcItem(uint id, uint itemId, ItemTypeEnum type, string name, string iconName, string description, int mana, uint weight, uint amount, bool isQuest, bool isAutoused) :
|
||||
base(id, itemId, type, name, iconName, description, mana, weight)
|
||||
|
@ -70,6 +70,14 @@ namespace Client.Domain.Entities
|
||||
[JsonProperty("AttackerIds", ObjectCreationHandling = ObjectCreationHandling.Replace)]
|
||||
public List<uint> AttackerIds { get => attackerIds; set { if (!value.All(attackerIds.Contains) || !attackerIds.All(value.Contains)) { attackerIds = value; OnPropertyChanged("AttackerIds"); } } }
|
||||
|
||||
public bool HasValidTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
return Target != null && Target.IsHostile && !Target.VitalStats.IsDead;
|
||||
}
|
||||
}
|
||||
|
||||
public Hero(uint id, Transform transform, FullName fullName, VitalStats vitalStats, Phenotype phenotype, ExperienceInfo experienceInfo, PermanentStats permanentStats, VariableStats variableStats, Reputation reputation, InventoryInfo inventoryInfo, uint targetId, bool isStanding)
|
||||
{
|
||||
Id = id;
|
||||
|
@ -16,9 +16,6 @@ namespace Client.Domain.Entities
|
||||
string Description { get; set; }
|
||||
int Mana { get; set; }
|
||||
uint Weight { get; set; }
|
||||
uint Amount { get; set; }
|
||||
bool IsQuest { get; set; }
|
||||
bool IsAutoused { get; set; }
|
||||
string FullDescription { get; }
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace Client.Domain.Entities
|
||||
public Transform Transform { get; set; }
|
||||
public bool IsHostile { get; set; }
|
||||
public uint NpcId { get; set; }
|
||||
public SpoilStateEnum SpoilState { get; set; }
|
||||
public SpoilStateEnum SpoilState { get { return spoilState; } set { if (spoilState != value) { spoilState = value; OnPropertyChanged(); } } }
|
||||
public FullName FullName
|
||||
{
|
||||
get => fullName;
|
||||
@ -134,5 +134,6 @@ namespace Client.Domain.Entities
|
||||
private uint aggroRadius;
|
||||
private VitalStats vitalStats;
|
||||
private FullName fullName;
|
||||
private SpoilStateEnum spoilState;
|
||||
}
|
||||
}
|
||||
|
28
Client/Domain/Entities/WeaponItem.cs
Normal file
28
Client/Domain/Entities/WeaponItem.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Client.Domain.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Entities
|
||||
{
|
||||
public class WeaponItem : BaseItem, ItemInterface
|
||||
{
|
||||
public WeaponTypeEnum WeaponType { get; set; }
|
||||
public CrystalTypeEnum CrystalType { get; set; }
|
||||
public byte SoulshotCount { get; set; }
|
||||
public byte SpiritshotCount { get; set; }
|
||||
public bool IsEquipped { get; set; }
|
||||
|
||||
public WeaponItem(uint id, uint itemId, ItemTypeEnum type, string name, string iconName, string description, int mana, uint weight, WeaponTypeEnum weaponType, CrystalTypeEnum crystalType, byte soulshotCount, byte spiritshotCount, bool isEquipped) :
|
||||
base(id, itemId, type, name, iconName, description, mana, weight)
|
||||
{
|
||||
WeaponType = weaponType;
|
||||
CrystalType = crystalType;
|
||||
SoulshotCount = soulshotCount;
|
||||
SpiritshotCount = spiritshotCount;
|
||||
IsEquipped = isEquipped;
|
||||
}
|
||||
}
|
||||
}
|
19
Client/Domain/Enums/CrystalTypeEnum.cs
Normal file
19
Client/Domain/Enums/CrystalTypeEnum.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Enums
|
||||
{
|
||||
public enum CrystalTypeEnum
|
||||
{
|
||||
None = -1,
|
||||
Ng,
|
||||
D,
|
||||
C,
|
||||
B,
|
||||
A,
|
||||
S
|
||||
}
|
||||
}
|
23
Client/Domain/Enums/WeaponTypeEnum.cs
Normal file
23
Client/Domain/Enums/WeaponTypeEnum.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Enums
|
||||
{
|
||||
public enum WeaponTypeEnum
|
||||
{
|
||||
None = 0,
|
||||
Sword,
|
||||
Blunt,
|
||||
Dagger,
|
||||
Pole,
|
||||
Fist,
|
||||
Bow,
|
||||
Etc,
|
||||
Dualsword,
|
||||
Pet,
|
||||
FishingRod
|
||||
}
|
||||
}
|
13
Client/Domain/Helpers/ItemInfo.cs
Normal file
13
Client/Domain/Helpers/ItemInfo.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Helpers
|
||||
{
|
||||
public class ItemInfo : ObjectInfo
|
||||
{
|
||||
public bool IsShot { get; internal set; }
|
||||
}
|
||||
}
|
13
Client/Domain/Helpers/ItemInfoHelperInterface.cs
Normal file
13
Client/Domain/Helpers/ItemInfoHelperInterface.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Helpers
|
||||
{
|
||||
public interface ItemInfoHelperInterface
|
||||
{
|
||||
List<ItemInfo> GetAllItems();
|
||||
}
|
||||
}
|
15
Client/Domain/Helpers/NpcInfo.cs
Normal file
15
Client/Domain/Helpers/NpcInfo.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Helpers
|
||||
{
|
||||
public class NpcInfo : ObjectInfo
|
||||
{
|
||||
public uint Level { get; internal set; }
|
||||
public uint AggroRadius { get; internal set; }
|
||||
public bool IsGuard { get; internal set; }
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Client.Infrastructure.Helpers.ConfigurationNpcInfoHelper;
|
||||
|
||||
namespace Client.Domain.Helpers
|
||||
{
|
||||
@ -10,5 +11,6 @@ namespace Client.Domain.Helpers
|
||||
{
|
||||
uint GetLevel(uint id);
|
||||
uint GetAggroRadius(uint id);
|
||||
List<NpcInfo> GetAllNpc();
|
||||
}
|
||||
}
|
||||
|
14
Client/Domain/Helpers/ObjectInfo.cs
Normal file
14
Client/Domain/Helpers/ObjectInfo.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Helpers
|
||||
{
|
||||
public class ObjectInfo
|
||||
{
|
||||
public uint Id { get; internal set; }
|
||||
public string Name { get; internal set; } = "";
|
||||
}
|
||||
}
|
13
Client/Domain/Helpers/SkillInfo.cs
Normal file
13
Client/Domain/Helpers/SkillInfo.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Helpers
|
||||
{
|
||||
public class SkillInfo : ObjectInfo
|
||||
{
|
||||
public bool IsActive { get; internal set; }
|
||||
}
|
||||
}
|
13
Client/Domain/Helpers/SkillInfoHelperInterface.cs
Normal file
13
Client/Domain/Helpers/SkillInfoHelperInterface.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Domain.Helpers
|
||||
{
|
||||
public interface SkillInfoHelperInterface
|
||||
{
|
||||
Dictionary<uint, SkillInfo> GetAllSkills();
|
||||
}
|
||||
}
|
@ -14,5 +14,8 @@ namespace Client.Domain.Service
|
||||
public ObservableCollection<PathSegment> Path { get; }
|
||||
public Task<bool> MoveAsync(Vector3 location);
|
||||
public Task MoveUntilReachedAsync(Vector3 location);
|
||||
public bool IsLocked { get; }
|
||||
|
||||
public void Unlock();
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
using Client.Domain.Transports;
|
||||
using Client.Domain.Common;
|
||||
using Client.Domain.Helpers;
|
||||
|
||||
namespace Client.Domain.Service
|
||||
{
|
||||
@ -184,6 +186,7 @@ namespace Client.Domain.Service
|
||||
|
||||
SendMessage(OutgoingMessageTypeEnum.Stand);
|
||||
}
|
||||
|
||||
public void RequestRestartPoint(RestartPointTypeEnum type)
|
||||
{
|
||||
if (hero == null)
|
||||
@ -194,13 +197,126 @@ namespace Client.Domain.Service
|
||||
SendMessage(OutgoingMessageTypeEnum.RestartPoint, type);
|
||||
}
|
||||
|
||||
|
||||
public List<NPC> GetAliveMobsSortedByDistanceToHero(uint deltaZ)
|
||||
{
|
||||
if (hero == null)
|
||||
{
|
||||
return new List<NPC> { };
|
||||
}
|
||||
|
||||
return creatures
|
||||
.Where(a =>
|
||||
{
|
||||
return a.Value is NPC
|
||||
&& a.Value.IsHostile
|
||||
&& !a.Value.VitalStats.IsDead
|
||||
&& MathF.Abs(a.Value.DeltaZ(hero)) <= deltaZ;
|
||||
})
|
||||
.OrderBy(a => a.Value.Distance(hero))
|
||||
.Select(a => (NPC)a.Value)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<NPC> GetDeadMobsSortedByDistanceToHero(uint deltaZ)
|
||||
{
|
||||
if (hero == null)
|
||||
{
|
||||
return new List<NPC> { };
|
||||
}
|
||||
|
||||
return creatures
|
||||
.Where(a =>
|
||||
{
|
||||
return a.Value is NPC
|
||||
&& a.Value.IsHostile
|
||||
&& a.Value.VitalStats.IsDead
|
||||
&& MathF.Abs(a.Value.DeltaZ(hero)) <= deltaZ;
|
||||
})
|
||||
.OrderBy(a => a.Value.Distance(hero))
|
||||
.Select(a => (NPC)a.Value)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<Drop> GetDropsSortedByDistanceToHero(uint deltaZ)
|
||||
{
|
||||
if (hero == null)
|
||||
{
|
||||
return new List<Drop> { };
|
||||
}
|
||||
|
||||
return drops
|
||||
.Where(x => MathF.Abs(x.Value.Transform.Position.Z - hero.Transform.Position.Z) <= deltaZ)
|
||||
.OrderBy(a => a.Value.Transform.Position.HorizontalDistance(hero.Transform.Position))
|
||||
.Select(a => a.Value)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public Skill? GetSkillById(uint id)
|
||||
{
|
||||
return skills.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public ItemInterface? GetItemById(uint id)
|
||||
{
|
||||
return items.Select(x => x.Value)
|
||||
.Where(x => x.ItemId == id)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<EtcItem> GetShotItems()
|
||||
{
|
||||
var shotIds = itemInfoHelper.GetAllItems()
|
||||
.Where(x => x.IsShot)
|
||||
.Select(x => x.Id)
|
||||
.ToDictionary(x => x, x => x);
|
||||
|
||||
return items.Select(x => x.Value)
|
||||
.Where(x => x is EtcItem && shotIds.ContainsKey(x.ItemId))
|
||||
.Cast<EtcItem>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<NPC> GetGuards()
|
||||
{
|
||||
if (hero == null)
|
||||
{
|
||||
return new List<NPC> { };
|
||||
}
|
||||
|
||||
var npcIds = npcInfoHelper.GetAllNpc()
|
||||
.Where(x => x.IsGuard)
|
||||
.Select(x => x.Id)
|
||||
.ToDictionary(x => x, x => x);
|
||||
|
||||
return creatures
|
||||
.Where(x =>
|
||||
{
|
||||
return x.Value is NPC && npcIds.ContainsKey(((NPC)x.Value).NpcId);
|
||||
})
|
||||
.Select(x => (NPC) x.Value)
|
||||
.OrderBy(x => x.Distance(hero))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public WeaponItem? GetEquippedWeapon()
|
||||
{
|
||||
return items.Select(x => x.Value)
|
||||
.Where(x =>
|
||||
{
|
||||
return x is WeaponItem
|
||||
&& ((WeaponItem)x).IsEquipped;
|
||||
})
|
||||
.Cast<WeaponItem>()
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private void SendMessage<T>(OutgoingMessageTypeEnum type, T? content = default)
|
||||
{
|
||||
var message = outgoingMessageBuilder.Build(
|
||||
new OutgoingMessage<T>(type, content)
|
||||
);
|
||||
transport.SendAsync(message);
|
||||
Debug.WriteLine(message);
|
||||
}
|
||||
|
||||
private void SendMessage(OutgoingMessageTypeEnum type)
|
||||
@ -272,10 +388,12 @@ namespace Client.Domain.Service
|
||||
}
|
||||
#endregion
|
||||
|
||||
public WorldHandler(OutgoingMessageBuilderInterface outgoingMessageBuilder, TransportInterface transport)
|
||||
public WorldHandler(OutgoingMessageBuilderInterface outgoingMessageBuilder, TransportInterface transport, ItemInfoHelperInterface itemInfoHelper, NpcInfoHelperInterface npcInfoHelper)
|
||||
{
|
||||
this.outgoingMessageBuilder = outgoingMessageBuilder;
|
||||
this.transport = transport;
|
||||
this.itemInfoHelper = itemInfoHelper;
|
||||
this.npcInfoHelper = npcInfoHelper;
|
||||
}
|
||||
|
||||
private Hero? hero;
|
||||
@ -285,5 +403,7 @@ namespace Client.Domain.Service
|
||||
private ConcurrentDictionary<uint, ItemInterface> items = new ConcurrentDictionary<uint, ItemInterface>();
|
||||
private readonly OutgoingMessageBuilderInterface outgoingMessageBuilder;
|
||||
private readonly TransportInterface transport;
|
||||
private ItemInfoHelperInterface itemInfoHelper;
|
||||
private readonly NpcInfoHelperInterface npcInfoHelper;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace Client.Domain.Transports
|
||||
bool IsConnected();
|
||||
Task ConnectAsync();
|
||||
Task SendAsync(string data);
|
||||
Task StartReceiveAsync();
|
||||
Task ReceiveAsync();
|
||||
|
||||
public delegate void DelegateMessage(string args);
|
||||
}
|
||||
|
@ -45,6 +45,14 @@ namespace Client.Domain.ValueObjects
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMoving
|
||||
{
|
||||
get
|
||||
{
|
||||
return !velocity.ApproximatelyEquals(Vector3.Zero, 0.0001f);
|
||||
}
|
||||
}
|
||||
|
||||
public Transform(Vector3 position, Vector3 rotation, Vector3 velocity, Vector3 acceleration)
|
||||
{
|
||||
this.position = position;
|
||||
|
@ -57,6 +57,31 @@ namespace Client.Domain.ValueObjects
|
||||
return equals;
|
||||
}
|
||||
|
||||
public float GetAngleBetweenDegree(Vector3 other)
|
||||
{
|
||||
return GetAngleBetween(other) / MathF.PI * 180;
|
||||
}
|
||||
|
||||
public float GetAngleBetween(Vector3 other)
|
||||
{
|
||||
return MathF.Acos(DotProduct(other) / (Distance(Zero) * other.Distance(Zero)));
|
||||
}
|
||||
|
||||
public float DotProduct(Vector3 other)
|
||||
{
|
||||
return x * other.x + y * other.y + z * other.z;
|
||||
}
|
||||
|
||||
public float Distance(Vector3 other)
|
||||
{
|
||||
return MathF.Sqrt(MathF.Pow(x - other.x, 2) + MathF.Pow(y - other.y, 2) + MathF.Pow(z - other.z, 2));
|
||||
}
|
||||
|
||||
public static Vector3 operator -(Vector3 left, Vector3 right)
|
||||
{
|
||||
return new Vector3(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
}
|
||||
|
||||
public static readonly Vector3 Zero = new Vector3(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
19
Client/Infrastructure/AI/IO/JsonConfigDeserializer.cs
Normal file
19
Client/Infrastructure/AI/IO/JsonConfigDeserializer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Client.Domain.AI;
|
||||
using Client.Domain.AI.IO;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Infrastructure.AI.IO
|
||||
{
|
||||
public class JsonConfigDeserializer : ConfigDeserializerInterface
|
||||
{
|
||||
public Config? Deserialize(string data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<Config>(data);
|
||||
}
|
||||
}
|
||||
}
|
20
Client/Infrastructure/AI/IO/JsonConfigSerializer.cs
Normal file
20
Client/Infrastructure/AI/IO/JsonConfigSerializer.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Client.Domain.AI;
|
||||
using Client.Domain.AI.IO;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Client.Infrastructure.AI.IO
|
||||
{
|
||||
public class JsonConfigSerializer : ConfigSerializerInterface
|
||||
{
|
||||
public string Serialize(Config config)
|
||||
{
|
||||
return JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings() { Culture = CultureInfo.InvariantCulture, ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ namespace Client.Infrastructure.Factories
|
||||
{
|
||||
case ItemTypeEnum.Etc:
|
||||
return JsonConvert.DeserializeObject<EtcItem>(data, settings);
|
||||
case ItemTypeEnum.Weapon:
|
||||
return JsonConvert.DeserializeObject<WeaponItem>(data, settings);
|
||||
default:
|
||||
return JsonConvert.DeserializeObject<EtcItem>(data, settings); //fixme temporary
|
||||
}
|
||||
|
54
Client/Infrastructure/Helpers/ConfigurationItemInfoHelper.cs
Normal file
54
Client/Infrastructure/Helpers/ConfigurationItemInfoHelper.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using Client.Domain.Helpers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Client.Infrastructure.Helpers
|
||||
{
|
||||
public class ConfigurationItemInfoHelper : ItemInfoHelperInterface
|
||||
{
|
||||
|
||||
public List<ItemInfo> GetAllItems()
|
||||
{
|
||||
LoadItems();
|
||||
return itemsInfo;
|
||||
}
|
||||
|
||||
public ConfigurationItemInfoHelper(IConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
private void LoadItems()
|
||||
{
|
||||
if (itemsInfo.Count == 0)
|
||||
{
|
||||
var items = configuration.GetRequiredSection("itemInfo").GetChildren();
|
||||
foreach (var item in items)
|
||||
{
|
||||
var id = uint.Parse(item.Key);
|
||||
|
||||
var isShot = false;
|
||||
if (item != null)
|
||||
{
|
||||
bool.TryParse(item.GetSection("isShot").Value, out isShot);
|
||||
}
|
||||
|
||||
itemsInfo.Add(new ItemInfo
|
||||
{
|
||||
Id = id,
|
||||
Name = string.Format("{0} [{1}]", item?.GetRequiredSection("name").Value ?? "", id),
|
||||
IsShot = isShot
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IConfiguration configuration;
|
||||
private List<ItemInfo> itemsInfo = new List<ItemInfo>();
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using static Client.Infrastructure.Helpers.ConfigurationExperienceHelper;
|
||||
|
||||
namespace Client.Infrastructure.Helpers
|
||||
{
|
||||
@ -14,12 +13,30 @@ namespace Client.Infrastructure.Helpers
|
||||
{
|
||||
public uint GetLevel(uint id)
|
||||
{
|
||||
return GetNpcInfo(id).level;
|
||||
LoadNpc();
|
||||
|
||||
if (npcInfo.ContainsKey(id))
|
||||
{
|
||||
return npcInfo[id].Level;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint GetAggroRadius(uint id)
|
||||
{
|
||||
return GetNpcInfo(id).aggroRadius;
|
||||
LoadNpc();
|
||||
|
||||
if (npcInfo.ContainsKey(id))
|
||||
{
|
||||
return npcInfo[id].AggroRadius;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public List<NpcInfo> GetAllNpc()
|
||||
{
|
||||
LoadNpc();
|
||||
return npcInfo.Select(x => x.Value).ToList();
|
||||
}
|
||||
|
||||
public ConfigurationNpcInfoHelper(IConfiguration configuration)
|
||||
@ -27,36 +44,37 @@ namespace Client.Infrastructure.Helpers
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
private NpcInfo GetNpcInfo(uint id)
|
||||
private void LoadNpc()
|
||||
{
|
||||
if (!npcInfo.ContainsKey(id))
|
||||
if (npcInfo.Count == 0)
|
||||
{
|
||||
var item = configuration.GetRequiredSection("npcInfo").GetChildren()
|
||||
.Where(x => x.Key == id.ToString())
|
||||
.FirstOrDefault();
|
||||
var items = configuration.GetRequiredSection("npcInfo").GetChildren();
|
||||
foreach (var item in items)
|
||||
{
|
||||
var id = uint.Parse(item.Key);
|
||||
|
||||
uint level = 0;
|
||||
uint aggroRadius = 0;
|
||||
bool isGuard = false;
|
||||
if (item != null)
|
||||
{
|
||||
uint.TryParse(item.GetRequiredSection("level").Value, out level);
|
||||
uint.TryParse(item.GetRequiredSection("aggroRadius").Value, out aggroRadius);
|
||||
bool.TryParse(item.GetRequiredSection("isGuard").Value, out isGuard);
|
||||
}
|
||||
npcInfo[id] = new NpcInfo
|
||||
{
|
||||
level = level,
|
||||
aggroRadius = aggroRadius
|
||||
Id = id,
|
||||
Level = level,
|
||||
AggroRadius = aggroRadius,
|
||||
Name = string.Format("{0} [{1}]", item?.GetRequiredSection("name").Value ?? "", id),
|
||||
IsGuard = isGuard
|
||||
};
|
||||
}
|
||||
return npcInfo[id];
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IConfiguration configuration;
|
||||
private Dictionary<uint, NpcInfo> npcInfo = new Dictionary<uint, NpcInfo>();
|
||||
|
||||
private class NpcInfo
|
||||
{
|
||||
public uint level { get; set; }
|
||||
public uint aggroRadius { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
53
Client/Infrastructure/Helpers/ConfigurationSkillnfoHelper.cs
Normal file
53
Client/Infrastructure/Helpers/ConfigurationSkillnfoHelper.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Client.Domain.Helpers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Client.Infrastructure.Helpers
|
||||
{
|
||||
public class ConfigurationSkillInfoHelper : SkillInfoHelperInterface
|
||||
{
|
||||
|
||||
public Dictionary<uint, SkillInfo> GetAllSkills()
|
||||
{
|
||||
LoadSkills();
|
||||
return skillsInfo;
|
||||
}
|
||||
|
||||
public ConfigurationSkillInfoHelper(IConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
private void LoadSkills()
|
||||
{
|
||||
if (skillsInfo.Count == 0)
|
||||
{
|
||||
var items = configuration.GetRequiredSection("skillInfo").GetChildren();
|
||||
foreach (var item in items)
|
||||
{
|
||||
var id = uint.Parse(item.Key);
|
||||
|
||||
var isActive = false;
|
||||
if (item != null)
|
||||
{
|
||||
bool.TryParse(item.GetRequiredSection("IsActive").Value, out isActive);
|
||||
}
|
||||
skillsInfo[id]=(new SkillInfo
|
||||
{
|
||||
Id = id,
|
||||
Name = string.Format("{0} [{1}]", item?.GetRequiredSection("name").Value ?? "", id),
|
||||
IsActive = isActive
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IConfiguration configuration;
|
||||
private Dictionary<uint, SkillInfo> skillsInfo = new Dictionary<uint, SkillInfo>();
|
||||
}
|
||||
}
|
@ -28,7 +28,19 @@ namespace Client.Infrastructure.Service
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
public ObservableCollection<PathSegment> Path { get; private set; } = new ObservableCollection<PathSegment>();
|
||||
public bool IsBusy { get; private set; } = false;
|
||||
public bool IsLocked { get; private set; } = false;
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
IsLocked = false;
|
||||
if (cancellationTokenSource != null)
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
cancellationTokenSource.Dispose();
|
||||
cancellationTokenSource = null;
|
||||
Path.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MoveUntilReachedAsync(Vector3 location)
|
||||
{
|
||||
@ -41,7 +53,7 @@ namespace Client.Infrastructure.Service
|
||||
|
||||
public async Task<bool> MoveAsync(Vector3 location)
|
||||
{
|
||||
IsBusy = true;
|
||||
IsLocked = true;
|
||||
|
||||
if (cancellationTokenSource != null)
|
||||
{
|
||||
@ -55,11 +67,9 @@ namespace Client.Infrastructure.Service
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Debug.WriteLine("Find path start");
|
||||
Debug.WriteLine("Find path started");
|
||||
FindPath(location);
|
||||
Debug.WriteLine("Find path finish");
|
||||
Debug.WriteLine("Find path finished");
|
||||
|
||||
|
||||
foreach (var node in Path.ToList())
|
||||
@ -69,19 +79,21 @@ namespace Client.Infrastructure.Service
|
||||
var reached = await WaitForNodeReaching(cancellationToken, node);
|
||||
if (!reached)
|
||||
{
|
||||
IsBusy = false;
|
||||
IsLocked = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Path.Remove(node);
|
||||
}
|
||||
|
||||
IsBusy = false;
|
||||
IsLocked = false;
|
||||
return true;
|
||||
}, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
IsBusy = false;
|
||||
Debug.WriteLine("Path cancelled");
|
||||
IsLocked = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -118,13 +130,8 @@ namespace Client.Infrastructure.Service
|
||||
var hero = worldHandler.Hero;
|
||||
|
||||
var start = DateTime.Now;
|
||||
while (hero != null && !hero.Transform.Position.ApproximatelyEquals(node.To, nodeDistanceTolerance))
|
||||
while (!token.IsCancellationRequested && 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;
|
||||
@ -138,7 +145,9 @@ namespace Client.Infrastructure.Service
|
||||
return false;
|
||||
}
|
||||
}
|
||||
await Task.Delay(25);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
await Task.Delay(25, token);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -44,9 +44,9 @@ namespace Client.Infrastructure.Transports
|
||||
Debug.WriteLine("Connected to main pipe\n");
|
||||
}
|
||||
}
|
||||
public async Task StartReceiveAsync()
|
||||
public async Task ReceiveAsync()
|
||||
{
|
||||
while (IsConnected())
|
||||
if (IsConnected())
|
||||
{
|
||||
byte[] buffer = new byte[16384 * 2];
|
||||
int readBytes = await mainPipe!.ReadAsync(buffer, 0, buffer.Length);
|
||||
|
Loading…
Reference in New Issue
Block a user