Skip to content

Commit

Permalink
Merge pull request #585 from FFXIV-CombatReborn/defensedetection
Browse files Browse the repository at this point in the history
Add new config options, improve performance, and refactor
  • Loading branch information
LTS-FFXIV authored Jan 19, 2025
2 parents fa7690c + ebfa270 commit 6163d20
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 89 deletions.
24 changes: 17 additions & 7 deletions BasicRotations/Healer/SGE_Default.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ namespace RebornRotations.Healer;
public sealed class SGE_Default : SageRotation
{
#region Config Options
[RotationConfig(CombatType.PvE, Name = "Use Eukrasia when out of combat")]
public bool OOCEukrasia { get; set; } = true;

[RotationConfig(CombatType.PvE, Name = "Use Rhizomata when out of combat")]
public bool OOCRhizomata { get; set; } = false;

[RotationConfig(CombatType.PvE, Name = "Use spells with cast times to heal. (Ignored if you are the only healer in party)")]
public bool GCDHeal { get; set; } = false;

Expand Down Expand Up @@ -197,9 +203,10 @@ protected override bool HealSingleAbility(IAction nextGCD, out IAction? act)
}
}

var tank = PartyMembers.GetJobCategory(JobRole.Tank);
foreach (var t in tank)
var tank = PartyMembers.GetJobCategory(JobRole.Tank).ToList();
for (int i = 0; i < tank.Count; i++)
{
var t = tank[i];
if (Addersgall < 1 && t.GetHealthRatio() < OGCDTankHeal)
{
if (HaimaPvE.CanUse(out act)) return true;
Expand All @@ -210,16 +217,18 @@ protected override bool HealSingleAbility(IAction nextGCD, out IAction? act)
}
}

foreach (var t in tank)
for (int i = 0; i < tank.Count; i++)
{
var t = tank[i];
if (t.GetHealthRatio() < ZoeHeal)
{
if (ZoePvE.CanUse(out act)) return true;
}
}

foreach (var t in tank)
for (int i = 0; i < tank.Count; i++)
{
var t = tank[i];
if (t.GetHealthRatio() < KrasisTankHeal)
{
if (KrasisPvE.CanUse(out act)) return true;
Expand All @@ -244,8 +253,9 @@ protected override bool GeneralAbility(IAction nextGCD, out IAction? act)
if (!InCombat && !Player.HasStatus(true, StatusID.Kardia) && KardiaPvE.CanUse(out act)) return true;

if (KardiaPvE.CanUse(out act)) return true;

if (Addersgall <= 1 && RhizomataPvE.CanUse(out act)) return true;

if (OOCRhizomata && !InCombat && Addersgall <= 1 && RhizomataPvE.CanUse(out act)) return true;
if (InCombat && Addersgall <= 1 && RhizomataPvE.CanUse(out act)) return true;

if (SoteriaPvE.CanUse(out act) && PartyMembers.Any(b => b.HasStatus(true, StatusID.Kardion) && b.GetHealthRatio() < HealthSingleAbility)) return true;

Expand Down Expand Up @@ -452,7 +462,7 @@ protected override bool GeneralGCD(out IAction? act)
if ( DoEukrasianDosis(out act)) return true;
if (DosisPvE.CanUse(out act)) return true;

if (!InCombat && !Player.HasStatus(true, StatusID.Eukrasia) && EukrasiaPvE.CanUse(out act)) return true;
if (OOCEukrasia && !InCombat && !Player.HasStatus(true, StatusID.Eukrasia) && EukrasiaPvE.CanUse(out act)) return true;
if (InCombat && !HasHostilesInRange && EukrasiaPvE.CanUse(out act)) return true;
return base.GeneralGCD(out act);
}
Expand Down
148 changes: 116 additions & 32 deletions RotationSolver.Basic/Actions/ActionTargetInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,19 @@ private IEnumerable<IBattleChara> GetCanTargets(bool skipStatusProvideCheck, Tar
var playerObjectId = Player.Object?.GameObjectId;
var targetObjectId = Svc.Targets.Target?.GameObjectId;

return validTargets.Where(b => isAuto || b.GameObjectId == targetObjectId || b.GameObjectId == playerObjectId)
.Where(InViewTarget)
.Where(CanUseTo)
.Where(action.Setting.CanTarget)
.ToList();
var result = new List<IBattleChara>();
foreach (var b in validTargets)
{
if (isAuto || b.GameObjectId == targetObjectId || b.GameObjectId == playerObjectId)
{
if (InViewTarget(b) && CanUseTo(b) && action.Setting.CanTarget(b))
{
result.Add(b);
}
}
}

return result;
}

/// <summary>
Expand All @@ -98,7 +106,15 @@ private List<IBattleChara> GetCanAffects(bool skipStatusProvideCheck, TargetType

if (type == TargetType.Heal)
{
items = items.Where(i => i.GetHealthRatio() < 1);
var filteredItems = new List<IBattleChara>();
foreach (var i in items)
{
if (i.GetHealthRatio() < 1)
{
filteredItems.Add(i);
}
}
items = filteredItems;
}

var validTargets = new List<IBattleChara>(items.Count());
Expand Down Expand Up @@ -391,12 +407,33 @@ private bool CheckTimeToKill(IGameObject gameObject)
{
if (canTargets == null || canAffects == null) return null;

var target = GetMostCanTargetObjects(canTargets, canAffects, aoeCount)
.OrderByDescending(ObjectHelper.GetHealthRatio).FirstOrDefault();
IBattleChara? target = null;
var mostCanTargetObjects = GetMostCanTargetObjects(canTargets, canAffects, aoeCount);
var enumerator = mostCanTargetObjects.GetEnumerator();

while (enumerator.MoveNext())
{
var t = enumerator.Current;
if (target == null || ObjectHelper.GetHealthRatio(t) > ObjectHelper.GetHealthRatio(target))
{
target = t;
}
}

if (target == null) return null;

return new TargetResult(target, GetAffects(target, canAffects).ToArray(), target.Position);
var affectedTargets = new List<IBattleChara>();
var affectsEnumerator = canAffects.GetEnumerator();
while (affectsEnumerator.MoveNext())
{
var t = affectsEnumerator.Current;
if (Vector3.Distance(target.Position, t.Position) - t.HitboxRadius <= EffectRange)
{
affectedTargets.Add(t);
}
}

return new TargetResult(target, affectedTargets.ToArray(), target.Position);
}

/// <summary>
Expand Down Expand Up @@ -439,8 +476,17 @@ private bool CheckTimeToKill(IGameObject gameObject)
}
else
{
var availableCharas = DataCenter.AllTargets.Where(b => b.GameObjectId != player.GameObjectId);
var target = FindTargetByType(TargetFilter.GetObjectInRadius(availableCharas, range), TargetType.Move, action.Config.AutoHealRatio, action.Setting.SpecialType);
var availableCharas = new List<IBattleChara>();
foreach (var availableTarget in DataCenter.AllTargets)
{
if (availableTarget.GameObjectId != player.GameObjectId)
{
availableCharas.Add(availableTarget);
}
}

var targetList = TargetFilter.GetObjectInRadius(availableCharas, range);
var target = FindTargetByType(targetList, TargetType.Move, action.Config.AutoHealRatio, action.Setting.SpecialType);
if (target == null) return null;

return new TargetResult(target, Array.Empty<IBattleChara>(), target.Position);
Expand All @@ -466,11 +512,10 @@ private bool CheckTimeToKill(IGameObject gameObject)
return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position);
}

var strategy = Service.Config.BeneficialAreaStrategy;
var strategy = Service.Config.BeneficialAreaStrategy2;
switch (strategy)
{
case BeneficialAreaStrategy.OnLocations: // Find from list
case BeneficialAreaStrategy.OnlyOnLocations: // Only the list
case BeneficialAreaStrategy2.OnLocations: // Only the list
OtherConfiguration.BeneficialPositions.TryGetValue(Svc.ClientState.TerritoryType, out var pts);
pts ??= Array.Empty<Vector3>();

Expand All @@ -479,18 +524,42 @@ private bool CheckTimeToKill(IGameObject gameObject)
{
if (DataCenter.Territory?.ContentType == TerritoryContentType.Trials ||
(DataCenter.Territory?.ContentType == TerritoryContentType.Raids &&
DataCenter.AllianceMembers.Count(p => p is IPlayerCharacter) >= 8))
DataCenter.PartyMembers.Count(p => p is IPlayerCharacter) >= 8))
{
var fallbackPoints = new[] { Vector3.Zero, new Vector3(100, 0, 100) };
var closestFallback = fallbackPoints.MinBy(p => Vector3.Distance(player.Position, p));
pts = pts.Concat(new[] { closestFallback }).ToArray();
var closestFallback = fallbackPoints[0];
var minDistance = Vector3.Distance(player.Position, fallbackPoints[0]);

for (int i = 1; i < fallbackPoints.Length; i++)
{
var distance = Vector3.Distance(player.Position, fallbackPoints[i]);
if (distance < minDistance)
{
closestFallback = fallbackPoints[i];
minDistance = distance;
}
}

pts = new[] { closestFallback };
}
}

// Find the closest point and apply a random offset
if (pts.Length > 0)
{
var closest = pts.MinBy(p => Vector3.Distance(player.Position, p));
var closest = pts[0];
var minDistance = Vector3.Distance(player.Position, pts[0]);

for (int i = 1; i < pts.Length; i++)
{
var distance = Vector3.Distance(player.Position, pts[i]);
if (distance < minDistance)
{
closest = pts[i];
minDistance = distance;
}
}

var random = new Random();
var rotation = random.NextDouble() * Math.Tau;
var radius = random.NextDouble();
Expand All @@ -504,28 +573,43 @@ private bool CheckTimeToKill(IGameObject gameObject)
}
}

// Return null if strategy is OnlyOnLocations and no valid point is found
if (strategy == BeneficialAreaStrategy.OnlyOnLocations) return null;
break;

case BeneficialAreaStrategy.OnTarget: // Target
if (Svc.Targets.Target != null && Svc.Targets.Target.DistanceToPlayer() < range)
{
var target = Svc.Targets.Target as IBattleChara;
return new TargetResult(target, GetAffects(target?.Position, canAffects).ToArray(), target?.Position);
}
// Return null if strategy is OnLocations and no valid point is found
if (strategy == BeneficialAreaStrategy2.OnLocations) return null;
break;

case BeneficialAreaStrategy.OnCalculated: // OnCalculated
//case BeneficialAreaStrategy2.OnTarget: // Target
// if (Svc.Targets.Target != null && Svc.Targets.Target.DistanceToPlayer() < range)
// {
// var target = Svc.Targets.Target as IBattleChara;
// if (target != null && !target.HasPositional() && target.HitboxRadius <= 8)
// {
// return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position);
// }
// return new TargetResult(target, GetAffects(target?.Position, canAffects).ToArray(), target?.Position);
// }
// break;

case BeneficialAreaStrategy2.OnCalculated: // OnCalculated
if (Svc.Targets.Target is IBattleChara b && b.DistanceToPlayer() < range &&
b.IsBossFromIcon() && b.HasPositional() && b.HitboxRadius <= 8)
b.IsBossFromIcon() && b.HasPositional() && b.HitboxRadius <= 8)
{
return new TargetResult(b, GetAffects(b.Position, canAffects).ToArray(), b.Position);
// Ensure the player's position is within the range of the ability
if (Vector3.Distance(player.Position, b.Position) <= range)
{
return new TargetResult(b, GetAffects(b.Position, canAffects).ToArray(), b.Position);
}
else
{
// Adjust the position to be within the range
Vector3 directionToTarget = b.Position - player.Position;
Vector3 adjustedPosition = player.Position + directionToTarget / directionToTarget.Length() * range;
return new TargetResult(b, GetAffects(adjustedPosition, canAffects).ToArray(), adjustedPosition);
}
}
else
{
var effectRange = EffectRange;
var attackT = FindTargetByType(DataCenter.AllianceMembers.GetObjectInRadius(range + effectRange),
var attackT = FindTargetByType(DataCenter.PartyMembers.GetObjectInRadius(range + effectRange),
TargetType.BeAttacked, action.Config.AutoHealRatio, action.Setting.SpecialType);

if (attackT == null)
Expand Down
18 changes: 4 additions & 14 deletions RotationSolver.Basic/Configuration/Configs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ public const string
[ConditionBool, UI("Use movement speed increase abilities when out of combat.", Parent = nameof(UseAbility))]
private static readonly bool _autoSpeedOutOfCombat = true;

[ConditionBool, UI("Use beneficial ground-targeted actions", Parent = nameof(UseAbility))]
[ConditionBool, UI("Use beneficial ground-targeted actions", Filter = HealingActionCondition, Section = 3)]
private static readonly bool _useGroundBeneficialAbility = true;

[ConditionBool, UI("Use beneficial AoE actions when moving.", Parent = nameof(UseGroundBeneficialAbility))]
Expand Down Expand Up @@ -387,16 +387,6 @@ public const string
public bool UseAdditionalConditions { get; set; } = false;

#region Float
[UI("isLastAbilityTimer", Description = "Don't fuck with this if you dont know what it does",
Filter = Extra)]
[Range(0.100f, 2.500f, ConfigUnitType.Seconds, 0.001f)]
public float IsLastAbilityTimer { get; set; } = 0.800f;

[UI("isFirstAbilityTimer", Description = "Don't fuck with this if you dont know what it does",
Filter = Extra)]
[Range(0.100f, 2.500f, ConfigUnitType.Seconds, 0.001f)]
public float IsFirstAbilityTimer { get; set; } = 0.600f;

[UI("Auto turn off RSR when combat is over more for more then...",
Parent = nameof(AutoOffAfterCombat))]
[Range(0, 600, ConfigUnitType.Seconds)]
Expand Down Expand Up @@ -482,7 +472,7 @@ public const string
public int LessMPNoRaise { get; set; }

[UI("HP for standard deviation for using AoE heal.", Description = "Basically the health difference between a single party member and the whole party, used for deciding between healing a single party member or AOE healing. Leave this alone if you don't undertand its use.",
Filter = HealingActionCondition, Section = 3)]
Filter = Extra)]
[Range(0, 0.5f, ConfigUnitType.Percent, 0.02f)]
public float HealthDifference { get; set; } = 0.25f;

Expand Down Expand Up @@ -649,8 +639,8 @@ public const string
[Range(0, 10, ConfigUnitType.None)]
public int TargetingIndex { get; set; }

[UI("Beneficial AoE strategy", Parent = nameof(UseGroundBeneficialAbility))]
public BeneficialAreaStrategy BeneficialAreaStrategy { get; set; } = BeneficialAreaStrategy.OnTarget;
[UI("Beneficial AOE Logic", Parent = nameof(UseGroundBeneficialAbility))]
public BeneficialAreaStrategy2 BeneficialAreaStrategy2 { get; set; } = BeneficialAreaStrategy2.OnCalculated;

[UI("Number of hostiles", Parent = nameof(UseDefenseAbility),
PvEFilter = JobFilterType.Tank)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,20 @@ public RotationConfigFloat(ICustomRotation rotation, PropertyInfo property)
/// <returns><c>true</c> if the command was executed; otherwise, <c>false</c>.</returns>
public override bool DoCommand(IRotationConfigSet set, string str)
{
if (str == null) return false;
if (!base.DoCommand(set, str)) return false;

// Ensure the string has sufficient length before slicing
if (str.Length <= Name.Length) return false;
if (str == null || !base.DoCommand(set, str) || str.Length <= Name.Length)
{
return false;
}

string numStr = str[Name.Length..].Trim();

// Parse the float value and set it
if (float.TryParse(numStr, out float parsedValue))
{
Value = parsedValue.ToString(); // Convert float to string
if (UnitType == ConfigUnitType.Percent)
{
parsedValue /= 100.0f;
}
Value = parsedValue.ToString();
return true;
}

Expand Down
22 changes: 11 additions & 11 deletions RotationSolver.Basic/Data/BeneficialAreaStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
/// <summary>
/// The way to place the beneficial area action.
/// </summary>
public enum BeneficialAreaStrategy : byte
public enum BeneficialAreaStrategy2 : byte
{
/// <summary>
/// Should use predefined location.
/// </summary>
[Description("On predefined location")]
OnLocations,

/// <summary>
/// Should use only predefined location.
/// </summary>
[Description("Only on predefined location")]
OnlyOnLocations,
///// <summary>
///// Should use only predefined location.
///// </summary>
//[Description("Only on predefined List location")]
//OnlyOnLocations,

/// <summary>
/// Should use target.
/// </summary>
[Description("On target")]
OnTarget,
///// <summary>
///// Should use target.
///// </summary>
//[Description("On target")]
//OnTarget,

/// <summary>
/// Should use the calculated location.
Expand Down
Loading

0 comments on commit 6163d20

Please sign in to comment.