diff --git a/BossMod/ActionTweaks/SmartRotationTweak.cs b/BossMod/ActionTweaks/SmartRotationTweak.cs index e56d7d1a68..57d4b28474 100644 --- a/BossMod/ActionTweaks/SmartRotationTweak.cs +++ b/BossMod/ActionTweaks/SmartRotationTweak.cs @@ -30,7 +30,7 @@ public sealed class SmartRotationTweak(WorldState ws, AIHints hints) public Angle? GetSpellOrientation(uint spellId, WPos playerPos, bool targetIsSelf, WPos? targetPos, WPos targetLoc) { var data = Service.LuminaRow(spellId); - if (data == null || !data.Value.NeedToFaceTarget) // does not require facing + if (data == null || !data.Value.NeedToFaceTarget || data.Value.Range == 0) // does not require facing return null; if (data.Value.TargetArea) return Angle.FromDirection(targetLoc - playerPos); diff --git a/BossMod/Autorotation/MiscAI/AutoEngage.cs b/BossMod/Autorotation/MiscAI/AutoEngage.cs new file mode 100644 index 0000000000..d99b20dac0 --- /dev/null +++ b/BossMod/Autorotation/MiscAI/AutoEngage.cs @@ -0,0 +1,56 @@ +using BossMod.Autorotation.xan; + +namespace BossMod.Autorotation.MiscAI; + +public sealed class AutoPull(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +{ + public enum Track { QuestBattle, DeepDungeon, EpicEcho, Hunt } + + public static RotationModuleDefinition Definition() + { + var def = new RotationModuleDefinition("Misc AI: Auto-pull", "Automatically attack passive mobs in certain circumstances", "Misc", "xan", RotationModuleQuality.Basic, new(~0ul), 1000, Order: RotationModuleOrder.HighLevel, CanUseWhileRoleplaying: true); + + def.AbilityTrack(Track.QuestBattle, "Automatically attack solo duty bosses"); + def.AbilityTrack(Track.DeepDungeon, "Automatically attack deep dungeon bosses when solo"); + def.AbilityTrack(Track.EpicEcho, "Automatically attack all targets if the Epic Echo status is present (i.e. when unsynced)"); + def.AbilityTrack(Track.Hunt, "Automatically attack hunt marks once they have already been pulled"); + + return def; + } + + public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) + { + if (Player.InCombat || primaryTarget != null || World.Client.CountdownRemaining != null) + return; + + var enabled = false; + + if (strategy.Enabled(Track.QuestBattle)) + enabled |= Bossmods.ActiveModule?.Info?.Category == BossModuleInfo.Category.Quest; + + if (strategy.Enabled(Track.DeepDungeon)) + enabled |= Bossmods.ActiveModule?.Info?.Category == BossModuleInfo.Category.DeepDungeon && World.Party.WithoutSlot().Count() == 1; + + if (strategy.Enabled(Track.EpicEcho)) + enabled |= Player.Statuses.Any(s => s.ID == 2734); + + // TODO set HP threshold lower, or remove entirely? want to avoid getting one guy'd by an early puller + if (strategy.Enabled(Track.Hunt) && Bossmods.ActiveModule?.Info?.Category == BossModuleInfo.Category.Hunt && Bossmods.ActiveModule?.PrimaryActor is Actor p && p.InCombat && p.HPRatio < 0.95f) + { + Hints.SetPriority(p, 0); + primaryTarget = p; + return; + } + + if (enabled) + { + var bestEnemy = Hints.PotentialTargets.Where(t => t.Priority == AIHints.Enemy.PriorityUndesirable).MinBy(p => Player.DistanceToHitbox(p.Actor)); + if (bestEnemy != null) + { + bestEnemy.Priority = 0; + primaryTarget = bestEnemy.Actor; + } + } + } +} + diff --git a/BossMod/Autorotation/MiscAI/AutoFarm.cs b/BossMod/Autorotation/MiscAI/AutoFarm.cs index 1a8464fb2c..f2e8649521 100644 --- a/BossMod/Autorotation/MiscAI/AutoFarm.cs +++ b/BossMod/Autorotation/MiscAI/AutoFarm.cs @@ -27,6 +27,10 @@ public static RotationModuleDefinition Definition() return res; } + // these mobs give fate XP and reward boost and should be prioritized over regular mobs - otherwise it's easy to accidentally complete the fate before killing them and lose the bonus + public const uint NameForlornMaiden = 6737; + public const uint NameTheForlorn = 6738; + public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { var generalOpt = strategy.Option(Track.General); @@ -64,7 +68,8 @@ void prioritize(AIHints.Enemy e, int prio) { if (e.Actor.FateID == World.Client.ActiveFate.ID && e.Priority == AIHints.Enemy.PriorityUndesirable) { - prioritize(e, 1); + var forlorn = e.Actor.NameID is NameForlornMaiden or NameTheForlorn; + prioritize(e, forlorn ? 2 : 1); } } }