Skip to content

Commit

Permalink
Core: FleeGoal: Added a way to custom define when the goal should be …
Browse files Browse the repository at this point in the history
…opt-in.

Core: Refactor a little bit.
  • Loading branch information
Xian55 committed Dec 15, 2024
1 parent 46a178d commit f1682ef
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 34 deletions.
1 change: 1 addition & 0 deletions Core/ClassConfig/ClassConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public sealed partial class ClassConfiguration
public Dictionary<string, int> IntVariables { get; } = new();

public KeyActions Pull { get; } = new();
public KeyActions Flee { get; } = new();
public KeyActions Combat { get; } = new();
public KeyActions Adhoc { get; } = new();
public KeyActions Parallel { get; } = new();
Expand Down
37 changes: 19 additions & 18 deletions Core/Goals/FleeGoal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

using Microsoft.Extensions.Logging;

using SharedLib.Extensions;

using System;
using System.Buffers;
using System.Numerics;
Expand All @@ -19,18 +21,15 @@ public sealed class FleeGoal : GoapGoal, IRouteProvider
private readonly PlayerReader playerReader;
private readonly Navigation navigation;
private readonly AddonBits bits;
private readonly CombatLog combatLog;

private readonly SafeSpotCollector safeSpotCollector;

private Vector3[] MapPoints = [];

public int MOB_COUNT = 1;

public FleeGoal(ILogger<CombatGoal> logger, ConfigurableInput input,
Wait wait, PlayerReader playerReader, AddonBits bits,
ClassConfiguration classConfiguration, Navigation playerNavigation,
ClassConfiguration classConfig, CombatLog combatLog,
ClassConfiguration classConfig,
SafeSpotCollector safeSpotCollector)
: base(nameof(FleeGoal))
{
Expand All @@ -41,15 +40,13 @@ public FleeGoal(ILogger<CombatGoal> logger, ConfigurableInput input,
this.playerReader = playerReader;
this.navigation = playerNavigation;
this.bits = bits;
this.combatLog = combatLog;

this.classConfig = classConfig;

AddPrecondition(GoapKey.incombat, true);

Keys = classConfiguration.Combat.Sequence;
Keys = classConfiguration.Flee.Sequence;

// this will ensure that the component is created
this.safeSpotCollector = safeSpotCollector;
}

Expand Down Expand Up @@ -79,28 +76,32 @@ public Vector3 NextMapPoint()
public override bool CanRun()
{
return
safeSpotCollector.Locations.Count > 0 &&
combatLog.DamageTakenCount() > MOB_COUNT;
safeSpotCollector.MapLocations.Count > 0 &&
Keys.Length > 0 && Keys[0].CanRun();
}

public override void OnEnter()
{
// TODO: might have to do some pre processing like
// straightening the path like
// PathSimplify.SimplifyPath(MapPoints);
var count = safeSpotCollector.Locations.Count;
MapPoints = new Vector3[count];
int count = safeSpotCollector.MapLocations.Count;

ArrayPool<Vector3> pooler = ArrayPool<Vector3>.Shared;
Vector3[] array = pooler.Rent(count);
var span = array.AsSpan();

safeSpotCollector.Locations.CopyTo(MapPoints, 0);
safeSpotCollector.MapLocations.CopyTo(array, 0);

navigation.SetWayPoints(MapPoints.AsSpan(0, count));
Span<Vector3> simplified = PathSimplify.Simplify(array.AsSpan()[..count], PathSimplify.HALF, true);
MapPoints = simplified.ToArray();

navigation.SetWayPoints(simplified);
navigation.ResetStuckParameters();

pooler.Return(array);
}

public override void OnExit()
{
// TODO: there might be better options here to dont clear all of them
safeSpotCollector.Locations.Clear();
safeSpotCollector.Reduce(playerReader.MapPosNoZ);

navigation.Stop();
navigation.StopMovement();
Expand Down
8 changes: 2 additions & 6 deletions Core/GoalsComponent/Navigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -419,14 +419,10 @@ private float ReachedDistance(float minDistance)

private void ReduceByDistance(Vector3 playerW, float minDistance)
{
float worldDistance = playerW.WorldDistanceXYTo(routeToNextWaypoint.Peek());
while (worldDistance < ReachedDistance(minDistance) && routeToNextWaypoint.Count > 0)
while (routeToNextWaypoint.Count > 0 &&
playerW.WorldDistanceXYTo(routeToNextWaypoint.Peek()) < ReachedDistance(minDistance))
{
routeToNextWaypoint.Pop();
if (routeToNextWaypoint.Count > 0)
{
worldDistance = playerW.WorldDistanceXYTo(routeToNextWaypoint.Peek());
}
}
}

Expand Down
35 changes: 29 additions & 6 deletions Core/GoalsComponent/SafeSpotCollector.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Core.GOAP;
using SharedLib.Extensions;

using System;
using System.Collections.Generic;
Expand All @@ -14,7 +14,7 @@ public sealed class SafeSpotCollector : IDisposable

private readonly Timer timer;

public Stack<Vector3> Locations { get; } = new();
public Stack<Vector3> MapLocations { get; } = new();

public SafeSpotCollector(
PlayerReader playerReader,
Expand All @@ -36,11 +36,34 @@ public void Update(object? obj)
if (bits.Combat())
return;

if (Locations.TryPeek(out var lastPos) &&
lastPos == playerReader.MapPosNoZ)
if (MapLocations.TryPeek(out Vector3 lastMapPos) &&
lastMapPos == playerReader.MapPosNoZ)
return;

// TODO: might be a good idea to check distance to last location
Locations.Push(playerReader.MapPosNoZ);
MapLocations.Push(playerReader.MapPosNoZ);
}

public void Reduce(Vector3 mapPosition)
{
lock (MapLocations)
{
Vector3 closestM = default;
float distanceM = float.MaxValue;

foreach (Vector3 p in MapLocations)
{
float d = mapPosition.MapDistanceXYTo(p);
if (d < distanceM)
{
closestM = p;
distanceM = d;
}
}

while (MapLocations.TryPeek(out var p) && p != closestM)
{
MapLocations.Pop();
}
}
}
}
11 changes: 9 additions & 2 deletions Core/GoalsFactory/GoalFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ public static IServiceProvider Create(
{
services.AddScoped<GoapGoal, WalkToCorpseGoal>();
services.AddScoped<GoapGoal, CombatGoal>();
services.AddScoped<GoapGoal, FleeGoal>();
services.AddScoped<GoapGoal, ApproachTargetGoal>();
services.AddScoped<GoapGoal, WaitForGatheringGoal>();
ResolveFollowRouteGoal(services, classConfig);
Expand Down Expand Up @@ -138,7 +137,7 @@ public static IServiceProvider Create(
services.AddScoped<GoapGoal, WalkToCorpseGoal>();
services.AddScoped<GoapGoal, PullTargetGoal>();
services.AddScoped<GoapGoal, ApproachTargetGoal>();
services.AddScoped<GoapGoal, FleeGoal>();
AddFleeGoal(services, classConfig);
services.AddScoped<GoapGoal, CombatGoal>();

if (classConfig.WrongZone.ZoneId > 0)
Expand Down Expand Up @@ -294,6 +293,14 @@ public static void ResolveFollowRouteGoal(IServiceCollection services,
}
}

public static void AddFleeGoal(IServiceCollection services, ClassConfiguration classConfig)
{
if (classConfig.Flee.Sequence.Length == 0)
return;

services.AddScoped<GoapGoal, FleeGoal>();
}

private static string RelativeFilePath(DataConfig dataConfig, string path)
{
return !path.Contains(dataConfig.Path)
Expand Down
7 changes: 5 additions & 2 deletions Core/Path/Simplify/PathSimplify.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ namespace Core;

public static class PathSimplify
{
public const float DEFAULT = 0.3f;
public const float HALF = 0.15f;

// square distance from a Vector3 to a segment
private static float GetSquareSegmentDistance(in Vector3 p, in Vector3 p1, in Vector3 p2)
{
Expand Down Expand Up @@ -129,10 +132,10 @@ private static Span<Vector3> DouglasPeucker(Span<Vector3> points, float sqTolera
/// <param name="tolerance">Tolerance tolerance in the same measurement as the Vector3 coordinates</param>
/// <param name="highestQuality">Enable highest quality for using Douglas-Peucker, set false for Radial-Distance algorithm</param>
/// <returns>Simplified list of Vector3</returns>
public static Span<Vector3> Simplify(Span<Vector3> points, float tolerance = 0.3f, bool highestQuality = false)
public static Span<Vector3> Simplify(Span<Vector3> points, float tolerance = DEFAULT, bool highestQuality = false)
{
if (points.Length == 0)
return Array.Empty<Vector3>();
return [];

float sqTolerance = tolerance * tolerance;

Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ Your class file probably exists and just needs to be edited to set the pathing f
| `"IntVariables"` | List of user defined `integer` variables | true | `[]` |
| --- | --- | --- | --- |
| `"Pull"` | [KeyActions](#keyactions) to execute upon [Pull Goal](#pull-goal) | true | `{}` |
| `"Flee"` | [KeyActions](#keyactions) to execute upon [Flee Goal](#flee-goal). Currently only the first one is considered for the custom logic. | true | `{}` |
| `"Combat"` | [KeyActions](#keyactions) to execute upon [Combat Goal](#combat-goal) | **false** | `{}` |
| `"AssistFocus"` | [KeyActions](#keyactions) to execute upon [Assist Focus Goal](#assist-focus-goal) | **false** | `{}` |
| `"Adhoc"` | [KeyActions](#keyactions) to execute upon [Adhoc Goals](#adhoc-goals) | true | `{}` |
Expand Down Expand Up @@ -900,6 +901,33 @@ e.g. of a Balance Druid
},
```

### Flee Goal

Its an opt-in goal.

Can define custom rules when the character should try to run away from an encounter which seems to be impossible to survive.

The goal will be executed while the player is in combat and the first KeyAction custom [Requirement(s)](#requirement) are met.

While the goal is active
* the player going to retrace the past locations which were deemed to be safe.
* Clears the current target if have any.

The path will be simplifed to ensure straight line of movement.

To opt-in the goal execution you have to define the following the [Class Configuration](#12-class-configuration)

```json
"Flee": {
"Sequence": [
{
"Name": "Flee",
"Requirement": "MobCount > 1 && Health% < 50"
}
]
},
```

### Combat Goal

The `Sequence` of [KeyAction(s)](#keyaction) that are used when in combat and trying to kill a mob.
Expand Down Expand Up @@ -1931,6 +1959,7 @@ e.g. Rogue_20.json
Every [KeyAction](#keyaction) has individual Interrupt(s) condition(s) which are [Requirement(s)](#requirement) to stop execution before fully finishing it.

As of now every [Goal groups](#goal-groups) has a default Interrupt.
* [Flee Goal](#flee-goal) based [KeyAction(s)](#keyaction) interrupted once the player left combat.
* [Combat Goal](#combat-goal) based [KeyAction(s)](#keyaction) interrupted once the target dies and the player loses the target.
* [Parallel Goal](#parallel-goals) based [KeyAction(s)](#keyaction) has **No** interrupt conditions.
* [Adhoc Goals](#adhoc-goals) based [KeyAction(s)](#keyaction) depends on `KeyAction.InCombat` flag.
Expand Down

0 comments on commit f1682ef

Please sign in to comment.