From eda5c4579be0ce5bd6c88b03ec58cfda9fca3bfa Mon Sep 17 00:00:00 2001 From: Aneesh Dogra Date: Thu, 12 Dec 2024 00:09:53 +0100 Subject: [PATCH 1/5] Add FleeGoal A goal which can be configured to run away to a possible safepoint if X mobs attack. --- Core/GOAP/GoapAgentState.cs | 5 +- Core/Goals/CombatGoal.cs | 36 +++++++- Core/Goals/FleeGoal.cs | 147 +++++++++++++++++++++++++++++++ Core/GoalsFactory/GoalFactory.cs | 1 + 4 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 Core/Goals/FleeGoal.cs diff --git a/Core/GOAP/GoapAgentState.cs b/Core/GOAP/GoapAgentState.cs index 386f5d59e..4f6024149 100644 --- a/Core/GOAP/GoapAgentState.cs +++ b/Core/GOAP/GoapAgentState.cs @@ -1,4 +1,6 @@ - +using System.Collections.Generic; +using System.Numerics; + namespace Core.GOAP; public sealed class GoapAgentState @@ -9,4 +11,5 @@ public sealed class GoapAgentState public int ConsumableCorpseCount { get; set; } public int LastCombatKillCount { get; set; } public bool Gathering { get; set; } + public LinkedList safeLocations = new LinkedList(); } diff --git a/Core/Goals/CombatGoal.cs b/Core/Goals/CombatGoal.cs index b4b4e1ae0..9d7eed602 100644 --- a/Core/Goals/CombatGoal.cs +++ b/Core/Goals/CombatGoal.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Numerics; namespace Core.Goals; @@ -21,6 +22,7 @@ public sealed class CombatGoal : GoapGoal, IGoapEventListener private readonly CastingHandler castingHandler; private readonly IMountHandler mountHandler; private readonly CombatLog combatLog; + private readonly GoapAgentState goapAgentState; private float lastDirection; private float lastMinDistance; @@ -29,7 +31,7 @@ public sealed class CombatGoal : GoapGoal, IGoapEventListener public CombatGoal(ILogger logger, ConfigurableInput input, Wait wait, PlayerReader playerReader, StopMoving stopMoving, AddonBits bits, ClassConfiguration classConfiguration, ClassConfiguration classConfig, - CastingHandler castingHandler, CombatLog combatLog, + CastingHandler castingHandler, CombatLog combatLog, GoapAgentState state, IMountHandler mountHandler) : base(nameof(CombatGoal)) { @@ -46,6 +48,8 @@ public CombatGoal(ILogger logger, ConfigurableInput input, this.mountHandler = mountHandler; this.classConfig = classConfig; + this.goapAgentState = state; + AddPrecondition(GoapKey.incombat, true); AddPrecondition(GoapKey.hastarget, true); AddPrecondition(GoapKey.targetisalive, true); @@ -162,6 +166,36 @@ public override void Update() stopMoving.Stop(); FindNewTarget(); } + else + { + // dont save too close points + logger.LogInformation("Target(s) Dead -- trying saving safe pos " + playerReader.MapPosNoZ.ToString()); + bool foundClosePoint = false; + if (this.goapAgentState.safeLocations == null) + { + return; + } + for (LinkedListNode point = this.goapAgentState.safeLocations.Last; point != null; point = point.Previous) + { + Vector2 p1 = new Vector2(point.Value.X, point.Value.Y); + Vector2 p2 = new Vector2(playerReader.MapPos.X, playerReader.MapPos.Y); + if (Vector2.Distance(p1, p2) <= 0.1) + { + foundClosePoint = true; + break; + } + } + if (!foundClosePoint) + { + Console.WriteLine("Saving safepos: " + playerReader.MapPosNoZ.ToString()); + this.goapAgentState.safeLocations.AddLast(playerReader.MapPosNoZ); + if (this.goapAgentState.safeLocations.Count > 100) + { + this.goapAgentState.safeLocations.RemoveFirst(); + } + } + input.PressClearTarget(); + } } } diff --git a/Core/Goals/FleeGoal.cs b/Core/Goals/FleeGoal.cs new file mode 100644 index 000000000..b437792db --- /dev/null +++ b/Core/Goals/FleeGoal.cs @@ -0,0 +1,147 @@ +using Core.GOAP; + +using Microsoft.Extensions.Logging; + +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Core.Goals; + +public sealed class FleeGoal : GoapGoal +{ + public override float Cost => 4f; + private readonly ILogger logger; + private readonly ConfigurableInput input; + private readonly ClassConfiguration classConfig; + private readonly Wait wait; + private readonly PlayerReader playerReader; + private readonly Navigation playerNavigation; + private readonly AddonBits bits; + private readonly StopMoving stopMoving; + private readonly CastingHandler castingHandler; + private readonly IMountHandler mountHandler; + private readonly CombatLog combatLog; + private readonly GoapAgentState goapAgentState; + public int MOB_COUNT = 1; + public bool runningAway; + + public FleeGoal(ILogger logger, ConfigurableInput input, + Wait wait, PlayerReader playerReader, StopMoving stopMoving, AddonBits bits, + ClassConfiguration classConfiguration, Navigation playerNavigation, GoapAgentState state, + ClassConfiguration classConfig, CastingHandler castingHandler, CombatLog combatLog, + IMountHandler mountHandler) + : base(nameof(CombatGoal)) + { + this.logger = logger; + this.input = input; + + this.runningAway = false; + this.wait = wait; + this.playerReader = playerReader; + this.playerNavigation = playerNavigation; + this.bits = bits; + this.combatLog = combatLog; + + this.stopMoving = stopMoving; + this.castingHandler = castingHandler; + this.mountHandler = mountHandler; + this.classConfig = classConfig; + this.goapAgentState = state; + + AddPrecondition(GoapKey.incombat, true); + //AddPrecondition(GoapKey.hastarget, true); + //AddPrecondition(GoapKey.targetisalive, true); + //AddPrecondition(GoapKey.targethostile, true); + //AddPrecondition(GoapKey.targettargetsus, true); + //AddPrecondition(GoapKey.incombatrange, true); + + //AddEffect(GoapKey.producedcorpse, true); + //AddEffect(GoapKey.targetisalive, false); + //AddEffect(GoapKey.hastarget, false); + + Keys = classConfiguration.Combat.Sequence; + } + + private void ResetCooldowns() + { + ReadOnlySpan span = Keys; + for (int i = 0; i < span.Length; i++) + { + KeyAction keyAction = span[i]; + if (keyAction.ResetOnNewTarget) + { + keyAction.ResetCooldown(); + keyAction.ResetCharges(); + } + } + } + + public override void OnEnter() + { + if (mountHandler.IsMounted()) + { + mountHandler.Dismount(); + } + + this.runningAway = false; + playerNavigation.Stop(); + playerNavigation.SetWayPoints(stackalloc Vector3[] { }); + playerNavigation.ResetStuckParameters(); + } + + public override void OnExit() + { + if (combatLog.DamageTakenCount() > 0 && !bits.Target()) + { + stopMoving.Stop(); + } + // clearing + this.runningAway = false; + playerNavigation.Stop(); + playerNavigation.SetWayPoints(stackalloc Vector3[] { }); + playerNavigation.ResetStuckParameters(); + } + + public override void Update() + { + wait.Update(); + if (this.goapAgentState.safeLocations.Count >= MOB_COUNT && this.runningAway == false) + { + + bool foundPoint = false; + logger.LogInformation("Flee Goal Activated. Current Pos: " + playerReader.MapPos.ToString() + ",Safe Spots: " + goapAgentState.safeLocations.Count); + if (goapAgentState.safeLocations == null) + { + return; + } + for (LinkedListNode point = goapAgentState.safeLocations.Last; point != null; point = point.Previous) + { + Vector2 p1 = new Vector2(point.Value.X, point.Value.Y); + Vector2 p2 = new Vector2(playerReader.MapPos.X, playerReader.MapPos.Y); + if (Vector2.Distance(p1, p2) >= 2.2) + { + // select the point far enough to lose the current mobs. + input.PressClearTarget(); + playerNavigation.Stop(); + playerNavigation.ResetStuckParameters(); + playerNavigation.SetWayPoints(stackalloc Vector3[] { (Vector3)(point.Value) }); + playerNavigation.Update(); + Console.WriteLine("Found point " + point.Value.ToString()); + foundPoint = true; + this.runningAway = true; + break; + } + } + if (foundPoint) + { + logger.LogInformation("Running away to the last safe point!"); + return; + } + else + { + logger.LogInformation("Cant run away, figting!"); + } + } + } +} diff --git a/Core/GoalsFactory/GoalFactory.cs b/Core/GoalsFactory/GoalFactory.cs index e1e55be92..0ff42a867 100644 --- a/Core/GoalsFactory/GoalFactory.cs +++ b/Core/GoalsFactory/GoalFactory.cs @@ -83,6 +83,7 @@ public static IServiceProvider Create( { services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); ResolveFollowRouteGoal(services, classConfig); From ca04ca32310eecf027ed24fa8e204b378b44159e Mon Sep 17 00:00:00 2001 From: Xian55 <367101+Xian55@users.noreply.github.com> Date: Thu, 12 Dec 2024 03:03:22 +0100 Subject: [PATCH 2/5] Refactor: FleeGoal: uses a slightly different implementation to better fit with the current project setup. --- Core/GOAP/GoapAgentState.cs | 3 +- Core/Goals/CombatGoal.cs | 31 +---- Core/Goals/FleeGoal.cs | 152 +++++++++-------------- Core/GoalsComponent/SafeSpotCollector.cs | 44 +++++++ Core/GoalsFactory/GoalFactory.cs | 2 + 5 files changed, 111 insertions(+), 121 deletions(-) create mode 100644 Core/GoalsComponent/SafeSpotCollector.cs diff --git a/Core/GOAP/GoapAgentState.cs b/Core/GOAP/GoapAgentState.cs index 4f6024149..f54c86ddb 100644 --- a/Core/GOAP/GoapAgentState.cs +++ b/Core/GOAP/GoapAgentState.cs @@ -11,5 +11,6 @@ public sealed class GoapAgentState public int ConsumableCorpseCount { get; set; } public int LastCombatKillCount { get; set; } public bool Gathering { get; set; } - public LinkedList safeLocations = new LinkedList(); + + public Stack SafeLocations { get; } = new(); } diff --git a/Core/Goals/CombatGoal.cs b/Core/Goals/CombatGoal.cs index 9d7eed602..073f0f26a 100644 --- a/Core/Goals/CombatGoal.cs +++ b/Core/Goals/CombatGoal.cs @@ -22,7 +22,6 @@ public sealed class CombatGoal : GoapGoal, IGoapEventListener private readonly CastingHandler castingHandler; private readonly IMountHandler mountHandler; private readonly CombatLog combatLog; - private readonly GoapAgentState goapAgentState; private float lastDirection; private float lastMinDistance; @@ -31,7 +30,7 @@ public sealed class CombatGoal : GoapGoal, IGoapEventListener public CombatGoal(ILogger logger, ConfigurableInput input, Wait wait, PlayerReader playerReader, StopMoving stopMoving, AddonBits bits, ClassConfiguration classConfiguration, ClassConfiguration classConfig, - CastingHandler castingHandler, CombatLog combatLog, GoapAgentState state, + CastingHandler castingHandler, CombatLog combatLog, IMountHandler mountHandler) : base(nameof(CombatGoal)) { @@ -48,8 +47,6 @@ public CombatGoal(ILogger logger, ConfigurableInput input, this.mountHandler = mountHandler; this.classConfig = classConfig; - this.goapAgentState = state; - AddPrecondition(GoapKey.incombat, true); AddPrecondition(GoapKey.hastarget, true); AddPrecondition(GoapKey.targetisalive, true); @@ -168,32 +165,6 @@ public override void Update() } else { - // dont save too close points - logger.LogInformation("Target(s) Dead -- trying saving safe pos " + playerReader.MapPosNoZ.ToString()); - bool foundClosePoint = false; - if (this.goapAgentState.safeLocations == null) - { - return; - } - for (LinkedListNode point = this.goapAgentState.safeLocations.Last; point != null; point = point.Previous) - { - Vector2 p1 = new Vector2(point.Value.X, point.Value.Y); - Vector2 p2 = new Vector2(playerReader.MapPos.X, playerReader.MapPos.Y); - if (Vector2.Distance(p1, p2) <= 0.1) - { - foundClosePoint = true; - break; - } - } - if (!foundClosePoint) - { - Console.WriteLine("Saving safepos: " + playerReader.MapPosNoZ.ToString()); - this.goapAgentState.safeLocations.AddLast(playerReader.MapPosNoZ); - if (this.goapAgentState.safeLocations.Count > 100) - { - this.goapAgentState.safeLocations.RemoveFirst(); - } - } input.PressClearTarget(); } } diff --git a/Core/Goals/FleeGoal.cs b/Core/Goals/FleeGoal.cs index b437792db..06787544d 100644 --- a/Core/Goals/FleeGoal.cs +++ b/Core/Goals/FleeGoal.cs @@ -3,145 +3,117 @@ using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; +using System.Buffers; using System.Numerics; namespace Core.Goals; -public sealed class FleeGoal : GoapGoal +public sealed class FleeGoal : GoapGoal, IRouteProvider { - public override float Cost => 4f; + public override float Cost => 3.1f; + private readonly ILogger logger; private readonly ConfigurableInput input; private readonly ClassConfiguration classConfig; private readonly Wait wait; private readonly PlayerReader playerReader; - private readonly Navigation playerNavigation; + private readonly Navigation navigation; private readonly AddonBits bits; - private readonly StopMoving stopMoving; - private readonly CastingHandler castingHandler; - private readonly IMountHandler mountHandler; private readonly CombatLog combatLog; private readonly GoapAgentState goapAgentState; + + private readonly SafeSpotCollector safeSpotCollector; + + private Vector3[] MapPoints = []; + public int MOB_COUNT = 1; - public bool runningAway; public FleeGoal(ILogger logger, ConfigurableInput input, - Wait wait, PlayerReader playerReader, StopMoving stopMoving, AddonBits bits, + Wait wait, PlayerReader playerReader, AddonBits bits, ClassConfiguration classConfiguration, Navigation playerNavigation, GoapAgentState state, - ClassConfiguration classConfig, CastingHandler castingHandler, CombatLog combatLog, - IMountHandler mountHandler) - : base(nameof(CombatGoal)) + ClassConfiguration classConfig, CombatLog combatLog, + SafeSpotCollector safeSpotCollector) + : base(nameof(FleeGoal)) { this.logger = logger; this.input = input; - this.runningAway = false; this.wait = wait; this.playerReader = playerReader; - this.playerNavigation = playerNavigation; + this.navigation = playerNavigation; this.bits = bits; this.combatLog = combatLog; - this.stopMoving = stopMoving; - this.castingHandler = castingHandler; - this.mountHandler = mountHandler; this.classConfig = classConfig; this.goapAgentState = state; AddPrecondition(GoapKey.incombat, true); - //AddPrecondition(GoapKey.hastarget, true); - //AddPrecondition(GoapKey.targetisalive, true); - //AddPrecondition(GoapKey.targethostile, true); - //AddPrecondition(GoapKey.targettargetsus, true); - //AddPrecondition(GoapKey.incombatrange, true); - - //AddEffect(GoapKey.producedcorpse, true); - //AddEffect(GoapKey.targetisalive, false); - //AddEffect(GoapKey.hastarget, false); Keys = classConfiguration.Combat.Sequence; + + // this will ensure that the component is created + this.safeSpotCollector = safeSpotCollector; + } + + #region IRouteProvider + + public DateTime LastActive => navigation.LastActive; + + public Vector3[] MapRoute() => MapPoints; + + public Vector3[] PathingRoute() + { + return navigation.TotalRoute; } - private void ResetCooldowns() + public bool HasNext() { - ReadOnlySpan span = Keys; - for (int i = 0; i < span.Length; i++) - { - KeyAction keyAction = span[i]; - if (keyAction.ResetOnNewTarget) - { - keyAction.ResetCooldown(); - keyAction.ResetCharges(); - } - } + return navigation.HasNext(); + } + + public Vector3 NextMapPoint() + { + return navigation.NextMapPoint(); + } + + #endregion + + public override bool CanRun() + { + return + goapAgentState.SafeLocations.Count > 0 && + combatLog.DamageTakenCount() > MOB_COUNT; } public override void OnEnter() { - if (mountHandler.IsMounted()) - { - mountHandler.Dismount(); - } + // TODO: might have to do some pre processing like + // straightening the path + var count = goapAgentState.SafeLocations.Count; + MapPoints = new Vector3[count]; + + goapAgentState.SafeLocations.CopyTo(MapPoints, 0); - this.runningAway = false; - playerNavigation.Stop(); - playerNavigation.SetWayPoints(stackalloc Vector3[] { }); - playerNavigation.ResetStuckParameters(); + navigation.SetWayPoints(MapPoints.AsSpan(0, count)); + navigation.ResetStuckParameters(); } public override void OnExit() { - if (combatLog.DamageTakenCount() > 0 && !bits.Target()) - { - stopMoving.Stop(); - } - // clearing - this.runningAway = false; - playerNavigation.Stop(); - playerNavigation.SetWayPoints(stackalloc Vector3[] { }); - playerNavigation.ResetStuckParameters(); + goapAgentState.SafeLocations.Clear(); + + navigation.Stop(); + navigation.StopMovement(); } public override void Update() { - wait.Update(); - if (this.goapAgentState.safeLocations.Count >= MOB_COUNT && this.runningAway == false) + if (bits.Target()) { - - bool foundPoint = false; - logger.LogInformation("Flee Goal Activated. Current Pos: " + playerReader.MapPos.ToString() + ",Safe Spots: " + goapAgentState.safeLocations.Count); - if (goapAgentState.safeLocations == null) - { - return; - } - for (LinkedListNode point = goapAgentState.safeLocations.Last; point != null; point = point.Previous) - { - Vector2 p1 = new Vector2(point.Value.X, point.Value.Y); - Vector2 p2 = new Vector2(playerReader.MapPos.X, playerReader.MapPos.Y); - if (Vector2.Distance(p1, p2) >= 2.2) - { - // select the point far enough to lose the current mobs. - input.PressClearTarget(); - playerNavigation.Stop(); - playerNavigation.ResetStuckParameters(); - playerNavigation.SetWayPoints(stackalloc Vector3[] { (Vector3)(point.Value) }); - playerNavigation.Update(); - Console.WriteLine("Found point " + point.Value.ToString()); - foundPoint = true; - this.runningAway = true; - break; - } - } - if (foundPoint) - { - logger.LogInformation("Running away to the last safe point!"); - return; - } - else - { - logger.LogInformation("Cant run away, figting!"); - } + input.PressClearTarget(); } + + wait.Update(); + navigation.Update(); } } diff --git a/Core/GoalsComponent/SafeSpotCollector.cs b/Core/GoalsComponent/SafeSpotCollector.cs new file mode 100644 index 000000000..d5c7cbc5a --- /dev/null +++ b/Core/GoalsComponent/SafeSpotCollector.cs @@ -0,0 +1,44 @@ +using Core.GOAP; + +using System; +using System.Threading; + +namespace Core.Goals; + +public sealed class SafeSpotCollector : IDisposable +{ + private readonly PlayerReader playerReader; + private readonly GoapAgentState state; + private readonly AddonBits bits; + + private readonly Timer timer; + + public SafeSpotCollector( + PlayerReader playerReader, + GoapAgentState state, + AddonBits bits) + { + this.playerReader = playerReader; + this.state = state; + this.bits = bits; + + timer = new(Update, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); + } + + public void Dispose() + { + timer.Dispose(); + } + + public void Update(object? obj) + { + if (bits.Combat()) + return; + + if (state.SafeLocations.TryPeek(out var lastPos) && + lastPos == playerReader.MapPosNoZ) + return; + + state.SafeLocations.Push(playerReader.MapPosNoZ); + } +} diff --git a/Core/GoalsFactory/GoalFactory.cs b/Core/GoalsFactory/GoalFactory.cs index 0ff42a867..fa6fbd1e7 100644 --- a/Core/GoalsFactory/GoalFactory.cs +++ b/Core/GoalsFactory/GoalFactory.cs @@ -57,6 +57,7 @@ public static IServiceProvider Create( services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); var playerReader = sp.GetRequiredService(); @@ -137,6 +138,7 @@ public static IServiceProvider Create( services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); if (classConfig.WrongZone.ZoneId > 0) From f063ae9643af8b35226b795daa5120f8ac3dedc2 Mon Sep 17 00:00:00 2001 From: Xian55 <367101+Xian55@users.noreply.github.com> Date: Thu, 12 Dec 2024 03:08:27 +0100 Subject: [PATCH 3/5] Added more notes --- Core/Goals/FleeGoal.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Goals/FleeGoal.cs b/Core/Goals/FleeGoal.cs index 06787544d..990113e1b 100644 --- a/Core/Goals/FleeGoal.cs +++ b/Core/Goals/FleeGoal.cs @@ -100,6 +100,7 @@ public override void OnEnter() public override void OnExit() { + // TODO: there might be better options here to dont clear all of them goapAgentState.SafeLocations.Clear(); navigation.Stop(); From 46a178d40ecdf6a9e3b78a3ea745ce4bc194fd68 Mon Sep 17 00:00:00 2001 From: Xian55 <367101+Xian55@users.noreply.github.com> Date: Thu, 12 Dec 2024 03:27:08 +0100 Subject: [PATCH 4/5] Refactor: GoapAgentState: no longer needed Refactor: SafeSpotCollector: stores the spots. More todos --- Core/GOAP/GoapAgentState.cs | 7 +------ Core/Goals/FleeGoal.cs | 15 +++++++-------- Core/GoalsComponent/SafeSpotCollector.cs | 12 +++++++----- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/Core/GOAP/GoapAgentState.cs b/Core/GOAP/GoapAgentState.cs index f54c86ddb..5d304769c 100644 --- a/Core/GOAP/GoapAgentState.cs +++ b/Core/GOAP/GoapAgentState.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Numerics; - -namespace Core.GOAP; +namespace Core.GOAP; public sealed class GoapAgentState { @@ -11,6 +8,4 @@ public sealed class GoapAgentState public int ConsumableCorpseCount { get; set; } public int LastCombatKillCount { get; set; } public bool Gathering { get; set; } - - public Stack SafeLocations { get; } = new(); } diff --git a/Core/Goals/FleeGoal.cs b/Core/Goals/FleeGoal.cs index 990113e1b..8d0742151 100644 --- a/Core/Goals/FleeGoal.cs +++ b/Core/Goals/FleeGoal.cs @@ -20,7 +20,6 @@ public sealed class FleeGoal : GoapGoal, IRouteProvider private readonly Navigation navigation; private readonly AddonBits bits; private readonly CombatLog combatLog; - private readonly GoapAgentState goapAgentState; private readonly SafeSpotCollector safeSpotCollector; @@ -30,7 +29,7 @@ public sealed class FleeGoal : GoapGoal, IRouteProvider public FleeGoal(ILogger logger, ConfigurableInput input, Wait wait, PlayerReader playerReader, AddonBits bits, - ClassConfiguration classConfiguration, Navigation playerNavigation, GoapAgentState state, + ClassConfiguration classConfiguration, Navigation playerNavigation, ClassConfiguration classConfig, CombatLog combatLog, SafeSpotCollector safeSpotCollector) : base(nameof(FleeGoal)) @@ -45,7 +44,6 @@ public FleeGoal(ILogger logger, ConfigurableInput input, this.combatLog = combatLog; this.classConfig = classConfig; - this.goapAgentState = state; AddPrecondition(GoapKey.incombat, true); @@ -81,18 +79,19 @@ public Vector3 NextMapPoint() public override bool CanRun() { return - goapAgentState.SafeLocations.Count > 0 && + safeSpotCollector.Locations.Count > 0 && combatLog.DamageTakenCount() > MOB_COUNT; } public override void OnEnter() { // TODO: might have to do some pre processing like - // straightening the path - var count = goapAgentState.SafeLocations.Count; + // straightening the path like + // PathSimplify.SimplifyPath(MapPoints); + var count = safeSpotCollector.Locations.Count; MapPoints = new Vector3[count]; - goapAgentState.SafeLocations.CopyTo(MapPoints, 0); + safeSpotCollector.Locations.CopyTo(MapPoints, 0); navigation.SetWayPoints(MapPoints.AsSpan(0, count)); navigation.ResetStuckParameters(); @@ -101,7 +100,7 @@ public override void OnEnter() public override void OnExit() { // TODO: there might be better options here to dont clear all of them - goapAgentState.SafeLocations.Clear(); + safeSpotCollector.Locations.Clear(); navigation.Stop(); navigation.StopMovement(); diff --git a/Core/GoalsComponent/SafeSpotCollector.cs b/Core/GoalsComponent/SafeSpotCollector.cs index d5c7cbc5a..0bf0d6498 100644 --- a/Core/GoalsComponent/SafeSpotCollector.cs +++ b/Core/GoalsComponent/SafeSpotCollector.cs @@ -1,6 +1,8 @@ using Core.GOAP; using System; +using System.Collections.Generic; +using System.Numerics; using System.Threading; namespace Core.Goals; @@ -8,18 +10,17 @@ namespace Core.Goals; public sealed class SafeSpotCollector : IDisposable { private readonly PlayerReader playerReader; - private readonly GoapAgentState state; private readonly AddonBits bits; private readonly Timer timer; + public Stack Locations { get; } = new(); + public SafeSpotCollector( PlayerReader playerReader, - GoapAgentState state, AddonBits bits) { this.playerReader = playerReader; - this.state = state; this.bits = bits; timer = new(Update, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); @@ -35,10 +36,11 @@ public void Update(object? obj) if (bits.Combat()) return; - if (state.SafeLocations.TryPeek(out var lastPos) && + if (Locations.TryPeek(out var lastPos) && lastPos == playerReader.MapPosNoZ) return; - state.SafeLocations.Push(playerReader.MapPosNoZ); + // TODO: might be a good idea to check distance to last location + Locations.Push(playerReader.MapPosNoZ); } } From f1682ef22657b6c73a98d2de81dadcd30785d9c8 Mon Sep 17 00:00:00 2001 From: Xian55 <367101+Xian55@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:31:48 +0100 Subject: [PATCH 5/5] Core: FleeGoal: Added a way to custom define when the goal should be opt-in. Core: Refactor a little bit. --- Core/ClassConfig/ClassConfiguration.cs | 1 + Core/Goals/FleeGoal.cs | 37 ++++++++++++------------ Core/GoalsComponent/Navigation.cs | 8 ++--- Core/GoalsComponent/SafeSpotCollector.cs | 35 ++++++++++++++++++---- Core/GoalsFactory/GoalFactory.cs | 11 +++++-- Core/Path/Simplify/PathSimplify.cs | 7 +++-- README.md | 29 +++++++++++++++++++ 7 files changed, 94 insertions(+), 34 deletions(-) diff --git a/Core/ClassConfig/ClassConfiguration.cs b/Core/ClassConfig/ClassConfiguration.cs index c6eaab7ce..77641ce3a 100644 --- a/Core/ClassConfig/ClassConfiguration.cs +++ b/Core/ClassConfig/ClassConfiguration.cs @@ -69,6 +69,7 @@ public sealed partial class ClassConfiguration public Dictionary 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(); diff --git a/Core/Goals/FleeGoal.cs b/Core/Goals/FleeGoal.cs index 8d0742151..19930e309 100644 --- a/Core/Goals/FleeGoal.cs +++ b/Core/Goals/FleeGoal.cs @@ -2,6 +2,8 @@ using Microsoft.Extensions.Logging; +using SharedLib.Extensions; + using System; using System.Buffers; using System.Numerics; @@ -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 logger, ConfigurableInput input, Wait wait, PlayerReader playerReader, AddonBits bits, ClassConfiguration classConfiguration, Navigation playerNavigation, - ClassConfiguration classConfig, CombatLog combatLog, + ClassConfiguration classConfig, SafeSpotCollector safeSpotCollector) : base(nameof(FleeGoal)) { @@ -41,15 +40,13 @@ public FleeGoal(ILogger 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; } @@ -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 pooler = ArrayPool.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 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(); diff --git a/Core/GoalsComponent/Navigation.cs b/Core/GoalsComponent/Navigation.cs index 403093705..9cceaa052 100644 --- a/Core/GoalsComponent/Navigation.cs +++ b/Core/GoalsComponent/Navigation.cs @@ -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()); - } } } diff --git a/Core/GoalsComponent/SafeSpotCollector.cs b/Core/GoalsComponent/SafeSpotCollector.cs index 0bf0d6498..2f47a346e 100644 --- a/Core/GoalsComponent/SafeSpotCollector.cs +++ b/Core/GoalsComponent/SafeSpotCollector.cs @@ -1,4 +1,4 @@ -using Core.GOAP; +using SharedLib.Extensions; using System; using System.Collections.Generic; @@ -14,7 +14,7 @@ public sealed class SafeSpotCollector : IDisposable private readonly Timer timer; - public Stack Locations { get; } = new(); + public Stack MapLocations { get; } = new(); public SafeSpotCollector( PlayerReader playerReader, @@ -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(); + } + } } } diff --git a/Core/GoalsFactory/GoalFactory.cs b/Core/GoalsFactory/GoalFactory.cs index fa6fbd1e7..a5da1286e 100644 --- a/Core/GoalsFactory/GoalFactory.cs +++ b/Core/GoalsFactory/GoalFactory.cs @@ -84,7 +84,6 @@ public static IServiceProvider Create( { services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); ResolveFollowRouteGoal(services, classConfig); @@ -138,7 +137,7 @@ public static IServiceProvider Create( services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + AddFleeGoal(services, classConfig); services.AddScoped(); if (classConfig.WrongZone.ZoneId > 0) @@ -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(); + } + private static string RelativeFilePath(DataConfig dataConfig, string path) { return !path.Contains(dataConfig.Path) diff --git a/Core/Path/Simplify/PathSimplify.cs b/Core/Path/Simplify/PathSimplify.cs index 83f175bbf..a196a915e 100644 --- a/Core/Path/Simplify/PathSimplify.cs +++ b/Core/Path/Simplify/PathSimplify.cs @@ -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) { @@ -129,10 +132,10 @@ private static Span DouglasPeucker(Span points, float sqTolera /// Tolerance tolerance in the same measurement as the Vector3 coordinates /// Enable highest quality for using Douglas-Peucker, set false for Radial-Distance algorithm /// Simplified list of Vector3 - public static Span Simplify(Span points, float tolerance = 0.3f, bool highestQuality = false) + public static Span Simplify(Span points, float tolerance = DEFAULT, bool highestQuality = false) { if (points.Length == 0) - return Array.Empty(); + return []; float sqTolerance = tolerance * tolerance; diff --git a/README.md b/README.md index 3bcb887bc..24997abdf 100644 --- a/README.md +++ b/README.md @@ -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 | `{}` | @@ -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. @@ -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.