diff --git a/BasicRotations/Healer/SGE_Default.cs b/BasicRotations/Healer/SGE_Default.cs index 91668c716..f9f122e5c 100644 --- a/BasicRotations/Healer/SGE_Default.cs +++ b/BasicRotations/Healer/SGE_Default.cs @@ -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; @@ -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; @@ -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; @@ -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; @@ -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); } diff --git a/RotationSolver.Basic/Actions/ActionTargetInfo.cs b/RotationSolver.Basic/Actions/ActionTargetInfo.cs index f7ffda9bd..c9c760873 100644 --- a/RotationSolver.Basic/Actions/ActionTargetInfo.cs +++ b/RotationSolver.Basic/Actions/ActionTargetInfo.cs @@ -69,11 +69,19 @@ private IEnumerable 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(); + 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; } /// @@ -98,7 +106,15 @@ private List GetCanAffects(bool skipStatusProvideCheck, TargetType if (type == TargetType.Heal) { - items = items.Where(i => i.GetHealthRatio() < 1); + var filteredItems = new List(); + foreach (var i in items) + { + if (i.GetHealthRatio() < 1) + { + filteredItems.Add(i); + } + } + items = filteredItems; } var validTargets = new List(items.Count()); @@ -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(); + 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); } /// @@ -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(); + 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(), target.Position); @@ -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(); @@ -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(); @@ -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) diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index 1494c94d2..cc9838a8c 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -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))] @@ -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)] @@ -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; @@ -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)] diff --git a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigFloat.cs b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigFloat.cs index 9d6636b07..a811f1871 100644 --- a/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigFloat.cs +++ b/RotationSolver.Basic/Configuration/RotationConfig/RotationConfigFloat.cs @@ -58,18 +58,20 @@ public RotationConfigFloat(ICustomRotation rotation, PropertyInfo property) /// true if the command was executed; otherwise, false. 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; } diff --git a/RotationSolver.Basic/Data/BeneficialAreaStrategy.cs b/RotationSolver.Basic/Data/BeneficialAreaStrategy.cs index 50c531ff8..78ec85ea1 100644 --- a/RotationSolver.Basic/Data/BeneficialAreaStrategy.cs +++ b/RotationSolver.Basic/Data/BeneficialAreaStrategy.cs @@ -3,7 +3,7 @@ /// /// The way to place the beneficial area action. /// -public enum BeneficialAreaStrategy : byte +public enum BeneficialAreaStrategy2 : byte { /// /// Should use predefined location. @@ -11,17 +11,17 @@ public enum BeneficialAreaStrategy : byte [Description("On predefined location")] OnLocations, - /// - /// Should use only predefined location. - /// - [Description("Only on predefined location")] - OnlyOnLocations, + ///// + ///// Should use only predefined location. + ///// + //[Description("Only on predefined List location")] + //OnlyOnLocations, - /// - /// Should use target. - /// - [Description("On target")] - OnTarget, + ///// + ///// Should use target. + ///// + //[Description("On target")] + //OnTarget, /// /// Should use the calculated location. diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs index 5776dfffe..4bbe4a827 100644 --- a/RotationSolver.Basic/DataCenter.cs +++ b/RotationSolver.Basic/DataCenter.cs @@ -815,7 +815,9 @@ public static bool IsCastingTankVfx() { return IsCastingVfx(VfxDataQueue, s => { - if (!s.Path.StartsWith("vfx/lockon/eff/tank_lockon")) return false; + if (!s.Path.StartsWith("vfx/lockon/eff/tank_lockon") + && !s.Path.StartsWith("vfx/lockon/eff/tank_laser")) return false; + if (!Player.Available) return false; if (Player.Object.IsJobCategory(JobRole.Tank) && s.ObjectId != Player.Object.GameObjectId) return false; return true; @@ -826,7 +828,10 @@ public static bool IsCastingAreaVfx() { return IsCastingVfx(VfxDataQueue, s => { - if (!s.Path.StartsWith("vfx/lockon/eff/coshare")) return false; + if (!s.Path.StartsWith("vfx/lockon/eff/coshare") + && !s.Path.StartsWith("vfx/lockon/eff/share_laser") + && !s.Path.StartsWith("vfx/lockon/eff/com_share")) return false; + if (!Player.Available) return false; return true; }); diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 067e313ab..b18362f10 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -2761,11 +2761,12 @@ private static unsafe void DrawStatus() ImGui.Text($"Is Casting Tank VFX: {DataCenter.IsCastingTankVfx()}"); ImGui.Text($"Is Casting Area VFX: {DataCenter.IsCastingAreaVfx()}"); ImGui.Text($"Is Hostile Casting Stop: {DataCenter.IsHostileCastingStop}"); + ImGui.Text($"VfxDataQueue: {DataCenter.VfxDataQueue.Count}"); // Check and display VFX casting status ImGui.Text("Casting Vfx:"); var filteredVfx = DataCenter.VfxDataQueue - .Where(s => s.Path.StartsWith("vfx/lockon/eff/coshare") && s.TimeDuration.TotalSeconds is > 1 and < 5); + .Where(s => s.Path.StartsWith("vfx/lockon/eff/") && s.TimeDuration.TotalSeconds is > 0 and < 6); foreach (var vfx in filteredVfx) { ImGui.Text($"Path: {vfx.Path}"); diff --git a/RotationSolver/UI/SearchableConfigs/DragFloatSearch.cs b/RotationSolver/UI/SearchableConfigs/DragFloatSearch.cs index 155effac3..5d4510068 100644 --- a/RotationSolver/UI/SearchableConfigs/DragFloatSearch.cs +++ b/RotationSolver/UI/SearchableConfigs/DragFloatSearch.cs @@ -12,24 +12,27 @@ public override string Description get { var baseDesc = base.Description; - if (!string.IsNullOrEmpty(baseDesc)) - { - return baseDesc + "\n" + Unit.ToString(); - } - else - { - return Unit.ToString(); - } + return !string.IsNullOrEmpty(baseDesc) ? baseDesc + "\n" + Unit.ToString() : Unit.ToString(); } } public DragFloatSearch(PropertyInfo property) : base(property) { var range = _property.GetCustomAttribute(); - Min = range?.MinValue ?? 0f; - Max = range?.MaxValue ?? 1f; - Speed = range?.Speed ?? 0.001f; - Unit = range?.UnitType ?? ConfigUnitType.None; + if (range != null) + { + Min = range.MinValue; + Max = range.MaxValue; + Speed = range.Speed; + Unit = range.UnitType; + } + else + { + Min = 0f; + Max = 1f; + Speed = 0.001f; + Unit = ConfigUnitType.None; + } } protected float Value @@ -49,9 +52,12 @@ protected override void DrawMain() // Draw slider or drag float based on unit type if (Unit == ConfigUnitType.Percent) { - if (ImGui.SliderFloat($"##Config_{ID}{hashCode}", ref value, Min, Max, $"{value * 100f:F1}{Unit.ToSymbol()}")) + // Convert the value to percentage for display + float displayValue = value * 100f; + if (ImGui.SliderFloat($"##Config_{ID}{hashCode}", ref displayValue, Min * 100f, Max * 100f, $"{displayValue:F1}{Unit.ToSymbol()}")) { - Value = value; + // Convert the display value back to the original scale + Value = displayValue / 100f; } } else diff --git a/RotationSolver/Updaters/MajorUpdater.cs b/RotationSolver/Updaters/MajorUpdater.cs index 2543f5e49..bc9e54669 100644 --- a/RotationSolver/Updaters/MajorUpdater.cs +++ b/RotationSolver/Updaters/MajorUpdater.cs @@ -191,7 +191,7 @@ private static void RemoveExpiredVfxData() for (int i = 0; i < DataCenter.VfxDataQueue.Count; i++) { var vfx = DataCenter.VfxDataQueue[i]; - if (vfx.TimeDuration > TimeSpan.FromSeconds(10)) + if (vfx.TimeDuration > TimeSpan.FromSeconds(6)) { expiredVfx.Add(vfx); }