feat: add combat and deleveling AI

This commit is contained in:
Иванов Иван
2024-08-15 17:23:24 +02:00
parent bdd026519f
commit 2943f7a50b
79 changed files with 61368 additions and 6746 deletions

View File

@@ -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>

View 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}"
>&#8595;</Button>
<Button
Width="20"
Command="{x:Static local:MultipleObjectSelector.RemoveItemCommand}"
CommandParameter="{Binding SelectedValue,ElementName=target}"
>&#8593;</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>

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

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

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

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

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

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

View File

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

View File

@@ -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(

View File

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

View File

@@ -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)
{
drop.Scale = scale;
drop.VieportSize = new Vector3((float)ViewportWidth, (float)ViewportHeight, 0);
}
foreach (var node in Path)
if (MapUpdated != null)
{
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;
}
}

View File

@@ -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),

View 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>&lt;= Player level &lt;=</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>

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

View File

@@ -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>

View File

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