feat: add max passable height to AI config

This commit is contained in:
Иванов Иван 2024-08-22 09:45:11 +02:00
parent d0baa5c21a
commit 936697defc
16 changed files with 46 additions and 39 deletions

View File

@ -120,8 +120,7 @@ namespace Client
.AddSingleton( .AddSingleton(
typeof(PathfinderInterface), typeof(PathfinderInterface),
x => new L2jGeoDataPathfinder( x => new L2jGeoDataPathfinder(
config.GetValue<string>("GeoDataDirectory") ?? "", config.GetValue<string>("GeoDataDirectory") ?? ""
config.GetValue<ushort>("MaxPassableHeight")
) )
) )
.AddSingleton( .AddSingleton(
@ -129,10 +128,10 @@ namespace Client
x => new AsyncPathMover( x => new AsyncPathMover(
x.GetRequiredService<WorldHandler>(), x.GetRequiredService<WorldHandler>(),
x.GetRequiredService<PathfinderInterface>(), x.GetRequiredService<PathfinderInterface>(),
config.GetValue<int>("PathNumberOfAttempts"),
config.GetValue<double>("NodeWaitingTime"), config.GetValue<double>("NodeWaitingTime"),
config.GetValue<int>("NodeDistanceTolerance"), config.GetValue<int>("NodeDistanceTolerance"),
config.GetValue<int>("NextNodeDistanceTolerance") config.GetValue<int>("NextNodeDistanceTolerance"),
config.GetValue<ushort>("MaxPassableHeight")
) )
) )

View File

@ -117,6 +117,7 @@ namespace Client.Application.ViewModels
public byte DelevelingTargetLevel { get => delevelingTargetLevel; set { if (value != delevelingTargetLevel) { delevelingTargetLevel = 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 DelevelingAttackDistance { get => delevelingAttackDistance; set { if (value != delevelingAttackDistance) { delevelingAttackDistance = value; OnPropertyChanged(); } } }
public uint DelevelingSkillId { get => delevelingSkillId; set { if (value != delevelingSkillId) { delevelingSkillId = value; OnPropertyChanged(); } } } public uint DelevelingSkillId { get => delevelingSkillId; set { if (value != delevelingSkillId) { delevelingSkillId = value; OnPropertyChanged(); } } }
public byte MaxPassableHeight { get => maxPassableHeight; set { if (value != maxPassableHeight) { maxPassableHeight = value; OnPropertyChanged(); } } }
public void LoadConfig() public void LoadConfig()
{ {
@ -151,6 +152,7 @@ namespace Client.Application.ViewModels
DelevelingTargetLevel = config.Deleveling.TargetLevel; DelevelingTargetLevel = config.Deleveling.TargetLevel;
DelevelingAttackDistance = config.Deleveling.AttackDistance; DelevelingAttackDistance = config.Deleveling.AttackDistance;
DelevelingSkillId = config.Deleveling.SkillId; DelevelingSkillId = config.Deleveling.SkillId;
MaxPassableHeight = config.Combat.MaxPassableHeight;
} }
private void SaveConfig() private void SaveConfig()
@ -181,6 +183,7 @@ namespace Client.Application.ViewModels
config.Deleveling.TargetLevel = DelevelingTargetLevel; config.Deleveling.TargetLevel = DelevelingTargetLevel;
config.Deleveling.AttackDistance = DelevelingAttackDistance; config.Deleveling.AttackDistance = DelevelingAttackDistance;
config.Deleveling.SkillId = DelevelingSkillId; config.Deleveling.SkillId = DelevelingSkillId;
config.Combat.MaxPassableHeight = MaxPassableHeight;
SaveCollections(); SaveCollections();
} }
@ -318,7 +321,7 @@ namespace Client.Application.ViewModels
private readonly ConfigDeserializerInterface configDeserializer; private readonly ConfigDeserializerInterface configDeserializer;
private uint mobsMaxDeltaZ = 0; private uint mobsMaxDeltaZ = 0;
private byte? mobLevelLowerLimit = null; private byte? mobLevelLowerLimit = null;
private byte? mobLevelUpperLimit = null; private byte maxPassableHeight = 0;
private bool spoilIfPossible = false; private bool spoilIfPossible = false;
private bool spoilIsPriority = false; private bool spoilIsPriority = false;
private uint spoilSkillId = 0; private uint spoilSkillId = 0;
@ -340,5 +343,6 @@ namespace Client.Application.ViewModels
private byte delevelingTargetLevel = 0; private byte delevelingTargetLevel = 0;
private uint delevelingAttackDistance = 0; private uint delevelingAttackDistance = 0;
private uint delevelingSkillId = 0; private uint delevelingSkillId = 0;
private byte? mobLevelUpperLimit = null;
} }
} }

View File

@ -43,7 +43,7 @@ namespace Client.Application.ViewModels
private async Task OnMouseRightClick(object? obj) private async Task OnMouseRightClick(object? obj)
{ {
await pathMover.MoveUntilReachedAsync(creature.Transform.Position); await pathMover.MoveAsync(creature.Transform.Position);
} }
public CreatureListViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, CreatureInterface creature, Hero hero) public CreatureListViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, CreatureInterface creature, Hero hero)

View File

@ -89,7 +89,7 @@ namespace Client.Application.ViewModels
private async Task OnMouseRightClick(object? obj) private async Task OnMouseRightClick(object? obj)
{ {
await pathMover.MoveUntilReachedAsync(creature.Transform.Position); await pathMover.MoveAsync(creature.Transform.Position);
} }
public CreatureMapViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, CreatureInterface creature, Hero hero) public CreatureMapViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, CreatureInterface creature, Hero hero)

View File

@ -76,7 +76,7 @@ namespace Client.Application.ViewModels
} }
private async Task OnMouseRightClick(object? obj) private async Task OnMouseRightClick(object? obj)
{ {
await pathMover.MoveUntilReachedAsync(drop.Transform.Position); await pathMover.MoveAsync(drop.Transform.Position);
} }
public DropListViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, Drop drop, Hero hero) public DropListViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, Drop drop, Hero hero)

View File

@ -64,7 +64,7 @@ namespace Client.Application.ViewModels
} }
private async Task OnMouseRightClick(object? obj) private async Task OnMouseRightClick(object? obj)
{ {
await pathMover.MoveUntilReachedAsync(drop.Transform.Position); await pathMover.MoveAsync(drop.Transform.Position);
} }
public DropMapViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, Drop drop, Hero hero) public DropMapViewModel(WorldHandler worldHandler, AsyncPathMoverInterface pathMover, Drop drop, Hero hero)

View File

@ -169,7 +169,7 @@ namespace Client.Application.ViewModels
hero.Transform.Position.Z hero.Transform.Position.Z
); );
await pathMover.MoveUntilReachedAsync(location); await pathMover.MoveAsync(location);
} }
public void OnMouseWheel(object sender, MouseWheelEventArgs e) public void OnMouseWheel(object sender, MouseWheelEventArgs e)

View File

@ -40,10 +40,19 @@
<RowDefinition Height="Auto"></RowDefinition> <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> </Grid.RowDefinitions>
<CheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding AutoUseShots}">Auto use soul and spiritshots</CheckBox> <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> <CheckBox Grid.Row="1" Grid.Column="0" IsChecked="{Binding UseOnlySkills}">Use only skills</CheckBox>
<StackPanel Grid.Row="2" Grid.Column="0"> <StackPanel Grid.Row="2" Grid.Column="0">
<Label>Pathfinder max passable height:</Label>
<TextBox Width="100" HorizontalAlignment="Left">
<TextBox.Text>
<Binding Path="MaxPassableHeight" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
</StackPanel>
<StackPanel Grid.Row="3" Grid.Column="0">
<Label>Attack distance for mili weapon:</Label> <Label>Attack distance for mili weapon:</Label>
<TextBox Width="100" HorizontalAlignment="Left"> <TextBox Width="100" HorizontalAlignment="Left">
<TextBox.Text> <TextBox.Text>
@ -51,7 +60,7 @@
</TextBox.Text> </TextBox.Text>
</TextBox> </TextBox>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="3" Grid.Column="0"> <StackPanel Grid.Row="4" Grid.Column="0">
<Label>Attack distance for bows:</Label> <Label>Attack distance for bows:</Label>
<TextBox Width="100" HorizontalAlignment="Left"> <TextBox Width="100" HorizontalAlignment="Left">
<TextBox.Text> <TextBox.Text>
@ -59,7 +68,7 @@
</TextBox.Text> </TextBox.Text>
</TextBox> </TextBox>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="4" Grid.Column="0"> <StackPanel Grid.Row="5" Grid.Column="0">
<Label>Skill conditions:</Label> <Label>Skill conditions:</Label>
<DataGrid <DataGrid
AutoGenerateColumns="False" AutoGenerateColumns="False"
@ -91,7 +100,7 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="5" Grid.Column="0"> <StackPanel Grid.Row="6" Grid.Column="0">
<Label>Combat zone:</Label> <Label>Combat zone:</Label>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>

View File

@ -33,6 +33,7 @@ namespace Client.Domain.AI
public uint AttackDistanceBow { get; set; } = 500; public uint AttackDistanceBow { get; set; } = 500;
public bool UseOnlySkills { get; set; } = false; public bool UseOnlySkills { get; set; } = false;
public List<SkillCondition> SkillConditions { get; set; } = new List<SkillCondition>(); public List<SkillCondition> SkillConditions { get; set; } = new List<SkillCondition>();
public byte MaxPassableHeight { get; set; } = 30;
public bool SpoilIfPossible { get; set; } = true; public bool SpoilIfPossible { get; set; } = true;
public bool SpoilIsPriority { get; set; } = false; public bool SpoilIsPriority { get; set; } = false;

View File

@ -31,7 +31,7 @@ namespace Client.Domain.AI.State
config.Combat.Zone.Center.X, config.Combat.Zone.Center.X,
config.Combat.Zone.Center.Y, config.Combat.Zone.Center.Y,
hero.Transform.Position.Z hero.Transform.Position.Z
)); ), config.Combat.MaxPassableHeight);
} }
} }
} }

View File

@ -37,7 +37,7 @@ namespace Client.Domain.AI.State
if (routeNeedsToBeAdjusted || distance >= Helper.GetAttackDistanceByConfig(worldHandler, config, hero, target) || !asyncPathMover.Pathfinder.HasLineOfSight(hero.Transform.Position, target.Transform.Position)) if (routeNeedsToBeAdjusted || distance >= Helper.GetAttackDistanceByConfig(worldHandler, config, hero, target) || !asyncPathMover.Pathfinder.HasLineOfSight(hero.Transform.Position, target.Transform.Position))
{ {
targetPosition = target.Transform.Position.Clone() as Vector3; targetPosition = target.Transform.Position.Clone() as Vector3;
asyncPathMover.MoveAsync(target.Transform.Position); asyncPathMover.MoveAsync(target.Transform.Position, config.Combat.MaxPassableHeight);
} }
} }

View File

@ -13,10 +13,9 @@ namespace Client.Domain.Service
{ {
public PathfinderInterface Pathfinder { get; } public PathfinderInterface Pathfinder { get; }
public ObservableCollection<PathSegment> Path { get; } public ObservableCollection<PathSegment> Path { get; }
public Task<bool> MoveAsync(Vector3 location, ushort maxPassableHeight);
public Task<bool> MoveAsync(Vector3 location); public Task<bool> MoveAsync(Vector3 location);
public Task MoveUntilReachedAsync(Vector3 location);
public bool IsLocked { get; } public bool IsLocked { get; }
public void Unlock(); public void Unlock();
} }
} }

View File

@ -10,7 +10,7 @@ namespace Client.Domain.Service
{ {
public interface PathfinderInterface public interface PathfinderInterface
{ {
public List<PathSegment> FindPath(Vector3 start, Vector3 end); public List<PathSegment> FindPath(Vector3 start, Vector3 end, ushort maxPassableHeight);
public bool HasLineOfSight(Vector3 start, Vector3 end); public bool HasLineOfSight(Vector3 start, Vector3 end);
} }
} }

View File

@ -21,10 +21,10 @@ namespace Client.Infrastructure.Service
{ {
private readonly WorldHandler worldHandler; private readonly WorldHandler worldHandler;
private readonly PathfinderInterface pathfinder; private readonly PathfinderInterface pathfinder;
private readonly int pathNumberOfAttempts;
private readonly double nodeWaitingTime; private readonly double nodeWaitingTime;
private readonly int nodeDistanceTolerance; private readonly int nodeDistanceTolerance;
private readonly int nextNodeDistanceTolerance; private readonly int nextNodeDistanceTolerance;
private readonly ushort maxPassableHeight;
private CancellationTokenSource? cancellationTokenSource; private CancellationTokenSource? cancellationTokenSource;
public PathfinderInterface Pathfinder => pathfinder; public PathfinderInterface Pathfinder => pathfinder;
@ -43,16 +43,7 @@ namespace Client.Infrastructure.Service
} }
} }
public async Task MoveUntilReachedAsync(Vector3 location) public async Task<bool> MoveAsync(Vector3 location, ushort maxPassableHeight)
{
var remainingAttempts = pathNumberOfAttempts;
while (!await MoveAsync(location) && remainingAttempts > 0)
{
remainingAttempts--;
}
}
public async Task<bool> MoveAsync(Vector3 location)
{ {
IsLocked = true; IsLocked = true;
@ -69,7 +60,7 @@ namespace Client.Infrastructure.Service
return await Task.Run(async () => return await Task.Run(async () =>
{ {
Debug.WriteLine("Find path started"); Debug.WriteLine("Find path started");
FindPath(location); FindPath(location, maxPassableHeight);
Debug.WriteLine("Find path finished"); Debug.WriteLine("Find path finished");
@ -99,17 +90,22 @@ namespace Client.Infrastructure.Service
} }
} }
public AsyncPathMover(WorldHandler worldHandler, PathfinderInterface pathfinder, int pathNumberOfAttempts, double nodeWaitingTime, int nodeDistanceTolerance, int nextNodeDistanceTolerance) public async Task<bool> MoveAsync(Vector3 location)
{
return await MoveAsync(location, maxPassableHeight);
}
public AsyncPathMover(WorldHandler worldHandler, PathfinderInterface pathfinder, double nodeWaitingTime, int nodeDistanceTolerance, int nextNodeDistanceTolerance, ushort maxPassableHeight)
{ {
this.worldHandler = worldHandler; this.worldHandler = worldHandler;
this.pathfinder = pathfinder; this.pathfinder = pathfinder;
this.pathNumberOfAttempts = pathNumberOfAttempts;
this.nodeWaitingTime = nodeWaitingTime; this.nodeWaitingTime = nodeWaitingTime;
this.nodeDistanceTolerance = nodeDistanceTolerance; this.nodeDistanceTolerance = nodeDistanceTolerance;
this.nextNodeDistanceTolerance = nextNodeDistanceTolerance; this.nextNodeDistanceTolerance = nextNodeDistanceTolerance;
this.maxPassableHeight = maxPassableHeight;
} }
private void FindPath(Vector3 location) private void FindPath(Vector3 location, ushort maxPassableHeight)
{ {
var hero = worldHandler.Hero; var hero = worldHandler.Hero;
@ -119,7 +115,7 @@ namespace Client.Infrastructure.Service
return; return;
} }
var path = pathfinder.FindPath(hero.Transform.Position, location); var path = pathfinder.FindPath(hero.Transform.Position, location, maxPassableHeight);
foreach (var segment in path) foreach (var segment in path)
{ {
Path.Add(segment); Path.Add(segment);

View File

@ -20,13 +20,12 @@ namespace Client.Infrastructure.Service
[DllImport("L2JGeoDataPathFinder.dll", CallingConvention = CallingConvention.Cdecl)] [DllImport("L2JGeoDataPathFinder.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern bool HasLineOfSight(string geoDataDirectory, float startX, float startY, float startZ, float endX, float endY, ushort maxPassableHeight); private static extern bool HasLineOfSight(string geoDataDirectory, float startX, float startY, float startZ, float endX, float endY, ushort maxPassableHeight);
public L2jGeoDataPathfinder(string geodataDirectory, ushort maxPassableHeight) public L2jGeoDataPathfinder(string geodataDirectory)
{ {
this.geodataDirectory = geodataDirectory; this.geodataDirectory = geodataDirectory;
this.maxPassableHeight = maxPassableHeight;
} }
public List<PathSegment> FindPath(Vector3 start, Vector3 end) public List<PathSegment> FindPath(Vector3 start, Vector3 end, ushort maxPassableHeight)
{ {
var arrayPtr = IntPtr.Zero; var arrayPtr = IntPtr.Zero;
var size = FindPath(out arrayPtr, GetGeodataFullpath(), start.X, start.Y, start.Z, end.X, end.Y, maxPassableHeight); var size = FindPath(out arrayPtr, GetGeodataFullpath(), start.X, start.Y, start.Z, end.X, end.Y, maxPassableHeight);
@ -53,7 +52,7 @@ namespace Client.Infrastructure.Service
public bool HasLineOfSight(Vector3 start, Vector3 end) public bool HasLineOfSight(Vector3 start, Vector3 end)
{ {
return HasLineOfSight(GetGeodataFullpath(), start.X, start.Y, start.Z, end.X, end.Y, maxPassableHeight); return HasLineOfSight(GetGeodataFullpath(), start.X, start.Y, start.Z, end.X, end.Y, LINE_OF_SIGHT_HEIGHT_OF_OBSTACLE);
} }
private List<PathSegment> BuildPath(List<PathNode> nodes) private List<PathSegment> BuildPath(List<PathNode> nodes)
@ -98,6 +97,6 @@ namespace Client.Infrastructure.Service
} }
private readonly string geodataDirectory; private readonly string geodataDirectory;
private readonly ushort maxPassableHeight; private const ushort LINE_OF_SIGHT_HEIGHT_OF_OBSTACLE = 999;
} }
} }

Binary file not shown.