From 9970fea95358c726ffd811327e0cfa3f99f0f635 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Fri, 7 Feb 2025 18:56:19 -0500 Subject: [PATCH 01/15] stop being "clever" with whm --- BossMod/Autorotation/Standard/xan/Healers/WHM.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/Healers/WHM.cs b/BossMod/Autorotation/Standard/xan/Healers/WHM.cs index bb6f7c8c62..ef145907e6 100644 --- a/BossMod/Autorotation/Standard/xan/Healers/WHM.cs +++ b/BossMod/Autorotation/Standard/xan/Healers/WHM.cs @@ -37,7 +37,6 @@ public static RotationModuleDefinition Definition() public int NumHolyTargets; public int NumAssizeTargets; public int NumMiseryTargets; - public int NumSolaceTargets; private Enemy? BestDotTarget; private Enemy? BestMiseryTarget; @@ -59,8 +58,6 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) (BestMiseryTarget, NumMiseryTargets) = SelectTarget(strategy, primaryTarget, 25, IsSplashTarget); (BestDotTarget, TargetDotLeft) = SelectDotTarget(strategy, primaryTarget, DotLeft, 2); - NumSolaceTargets = World.Party.WithoutSlot(excludeAlliance: true).Count(x => Player.DistanceToHitbox(x) <= 20); - if (CountdownRemaining > 0) { if (CountdownRemaining < GetCastTime(AID.Stone1)) @@ -93,7 +90,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) // TODO make a track for this if (Lily == 3 || !CanFitGCD(NextLily, 2) && Lily == 2) - PushGCD(AID.AfflatusSolace, World.Party.WithoutSlot(excludeAlliance: true).Where(m => Player.DistanceToHitbox(m) <= 30).MinBy(PredictedHPRatio)); + PushGCD(AID.AfflatusSolace, Player); if (SacredSight > 0) PushGCD(AID.GlareIV, primaryTarget); From 00d14b3e2c9fd8858b56c61bf8c88a7e3581d0bd Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Fri, 7 Feb 2025 20:28:09 -0500 Subject: [PATCH 02/15] support eureka fates --- BossMod/Autorotation/MiscAI/AutoFarm.cs | 2 +- .../Autorotation/Standard/xan/Casters/RDM.cs | 2 ++ BossMod/BossModule/AIHintsBuilder.cs | 3 +- BossMod/Data/ClientState.cs | 29 +++++++++++++++++++ BossMod/Framework/Utils.cs | 14 +++++++++ BossMod/Framework/WorldStateGameSync.cs | 13 +++++++++ BossMod/Replay/ReplayParserLog.cs | 9 ++++++ 7 files changed, 69 insertions(+), 3 deletions(-) diff --git a/BossMod/Autorotation/MiscAI/AutoFarm.cs b/BossMod/Autorotation/MiscAI/AutoFarm.cs index 1a8464fb2c..f89432f7c6 100644 --- a/BossMod/Autorotation/MiscAI/AutoFarm.cs +++ b/BossMod/Autorotation/MiscAI/AutoFarm.cs @@ -58,7 +58,7 @@ void prioritize(AIHints.Enemy e, int prio) // first deal with pulling new enemies if (allowPulling) { - if (World.Client.ActiveFate.ID != 0 && Player.Level <= Service.LuminaRow(World.Client.ActiveFate.ID)?.ClassJobLevelMax && strategy.Option(Track.Fate).As() == PriorityStrategy.Prioritize) + if (Utils.IsPlayerSyncedToFate(World) && strategy.Option(Track.Fate).As() == PriorityStrategy.Prioritize) { foreach (var e in Hints.PotentialTargets) { diff --git a/BossMod/Autorotation/Standard/xan/Casters/RDM.cs b/BossMod/Autorotation/Standard/xan/Casters/RDM.cs index a61bbae00f..6e6afc4dcb 100644 --- a/BossMod/Autorotation/Standard/xan/Casters/RDM.cs +++ b/BossMod/Autorotation/Standard/xan/Casters/RDM.cs @@ -126,6 +126,8 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) if (primaryTarget is { } tar && (Swordplay > 0 || LowestMana >= comboMana || InCombo)) Hints.GoalZones.Add(Hints.GoalSingleTarget(tar.Actor, 3)); + GoalZoneSingle(25); + OGCD(strategy, primaryTarget); if (ComboLastMove is AID.Scorch) diff --git a/BossMod/BossModule/AIHintsBuilder.cs b/BossMod/BossModule/AIHintsBuilder.cs index 2960b3778c..7277df63a1 100644 --- a/BossMod/BossModule/AIHintsBuilder.cs +++ b/BossMod/BossModule/AIHintsBuilder.cs @@ -63,8 +63,7 @@ public void Update(AIHints hints, int playerSlot, bool moveImminent) // fill list of potential targets from world state private void FillEnemies(AIHints hints, bool playerIsDefaultTank) { - var playerInFate = _ws.Client.ActiveFate.ID != 0 && _ws.Party.Player()?.Level <= Service.LuminaRow(_ws.Client.ActiveFate.ID)?.ClassJobLevelMax; - var allowedFateID = playerInFate ? _ws.Client.ActiveFate.ID : 0; + var allowedFateID = Utils.IsPlayerSyncedToFate(_ws) ? _ws.Client.ActiveFate.ID : 0; foreach (var actor in _ws.Actors.Where(a => a.IsTargetable && !a.IsAlly && !a.IsDead)) { var index = actor.CharacterSpawnIndex; diff --git a/BossMod/Data/ClientState.cs b/BossMod/Data/ClientState.cs index e5e4e48d67..e1250bfd00 100644 --- a/BossMod/Data/ClientState.cs +++ b/BossMod/Data/ClientState.cs @@ -58,6 +58,19 @@ public record struct DutyAction(ActionID Action, byte CurCharges, byte MaxCharge public Pet ActivePet; public ulong FocusTargetId; public Angle ForcedMovementDirection; // used for temporary misdirection and spinning states + public uint[] ContentKeyValueData = new uint[6]; // used for content-specific persistent player attributes, like bozja resistance rank + + public uint GetContentValue(uint key) => ContentKeyValueData[0] == key + ? ContentKeyValueData[1] + : ContentKeyValueData[2] == key + ? ContentKeyValueData[3] + : ContentKeyValueData[4] == key + ? ContentKeyValueData[5] + : 0; + + public uint ElementalLevel => GetContentValue(4); + public uint ElementalLevelSynced => GetContentValue(2); + public uint ResistanceRank => GetContentValue(5); public int ClassJobLevel(Class c) { @@ -355,4 +368,20 @@ protected override void Exec(WorldState ws) } public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("CLFD"u8).Emit(Value); } + + public Event ContentKVDataChanged = new(); + public sealed record class OpContentKVDataChange(uint[] Value) : WorldState.Operation + { + protected override void Exec(WorldState ws) + { + ws.Client.ContentKeyValueData = Value; + ws.Client.ContentKVDataChanged.Fire(this); + } + public override void Write(ReplayRecorder.Output output) + { + output.EmitFourCC("CLKV"u8); + foreach (var val in Value) + output.Emit(val); + } + } } diff --git a/BossMod/Framework/Utils.cs b/BossMod/Framework/Utils.cs index c8dbf26a7b..743d2ac837 100644 --- a/BossMod/Framework/Utils.cs +++ b/BossMod/Framework/Utils.cs @@ -50,6 +50,20 @@ public static string ObjectKindString(IGameObject obj) public static unsafe ulong SceneObjectFlags(FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object* o) => ReadField(o, 0x38); + public static bool IsPlayerSyncedToFate(WorldState world) + { + if (world.Client.ActiveFate.ID == 0) + return false; + + var fate = Service.LuminaRow(world.Client.ActiveFate.ID); + if (fate == null) + return false; + + return fate.Value.EurekaFate == 1 + ? world.Client.ElementalLevelSynced <= fate.Value.ClassJobLevelMax + : world.Party.Player()?.Level <= fate.Value.ClassJobLevelMax; + } + // lumina extensions public static int FindIndex(this Lumina.Excel.Collection collection, Func predicate) where T : struct { diff --git a/BossMod/Framework/WorldStateGameSync.cs b/BossMod/Framework/WorldStateGameSync.cs index c7157bc230..b012615d74 100644 --- a/BossMod/Framework/WorldStateGameSync.cs +++ b/BossMod/Framework/WorldStateGameSync.cs @@ -667,6 +667,19 @@ private unsafe void UpdateClient() var forcedMovementDir = MovementOverride.ForcedMovementDirection->Radians(); if (_ws.Client.ForcedMovementDirection != forcedMovementDir) _ws.Execute(new ClientState.OpForcedMovementDirectionChange(forcedMovementDir)); + + var contentKeyValue = uiState->PlayerState.ContentKeyValueData; + var ckArray = new uint[] + { + contentKeyValue[0].Item1, + contentKeyValue[0].Item2, + contentKeyValue[1].Item1, + contentKeyValue[1].Item2, + contentKeyValue[2].Item1, + contentKeyValue[2].Item2 + }; + if (!MemoryExtensions.SequenceEqual(ckArray, _ws.Client.ContentKeyValueData)) + _ws.Execute(new ClientState.OpContentKVDataChange(ckArray)); } private unsafe void UpdateDeepDungeon() diff --git a/BossMod/Replay/ReplayParserLog.cs b/BossMod/Replay/ReplayParserLog.cs index 8d839279ab..d58de0f42c 100644 --- a/BossMod/Replay/ReplayParserLog.cs +++ b/BossMod/Replay/ReplayParserLog.cs @@ -352,6 +352,7 @@ private ReplayParserLog(Input input, ReplayBuilder builder) [new("CPET"u8)] = ParseClientActivePet, [new("CLFT"u8)] = ParseClientFocusTarget, [new("CLFD"u8)] = ParseClientForcedMovementDirection, + [new("CLKV"u8)] = ParseClientContentKVData, [new("DDPG"u8)] = ParseDeepDungeonProgress, [new("DDMP"u8)] = ParseDeepDungeonMap, [new("DDPT"u8)] = ParseDeepDungeonParty, @@ -705,6 +706,14 @@ private ClientState.OpClassJobLevelsChange ParseClientClassJobLevels() private ClientState.OpActivePetChange ParseClientActivePet() => new(new(_input.ReadULong(true), _input.ReadByte(false), _input.ReadByte(false))); private ClientState.OpFocusTargetChange ParseClientFocusTarget() => new(_input.ReadULong(true)); private ClientState.OpForcedMovementDirectionChange ParseClientForcedMovementDirection() => new(_input.ReadAngle()); + private ClientState.OpContentKVDataChange ParseClientContentKVData() => new([ + _input.ReadUInt(false), + _input.ReadUInt(false), + _input.ReadUInt(false), + _input.ReadUInt(false), + _input.ReadUInt(false), + _input.ReadUInt(false), + ]); private DeepDungeonState.OpProgressChange ParseDeepDungeonProgress() => new((DeepDungeonState.DungeonType)_input.ReadByte(false), new(_input.ReadByte(false), _input.ReadByte(false), _input.ReadByte(false), _input.ReadByte(false), _input.ReadByte(false), _input.ReadByte(false), _input.ReadByte(false), _input.ReadByte(false))); private DeepDungeonState.OpMapDataChange ParseDeepDungeonMap() From b9bb49d5418a9d73ba2bd7dd049969f159451289 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Fri, 7 Feb 2025 20:32:11 -0500 Subject: [PATCH 03/15] compile error --- BossMod/Data/ClientState.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BossMod/Data/ClientState.cs b/BossMod/Data/ClientState.cs index e1250bfd00..b33d14ba6d 100644 --- a/BossMod/Data/ClientState.cs +++ b/BossMod/Data/ClientState.cs @@ -372,6 +372,7 @@ protected override void Exec(WorldState ws) public Event ContentKVDataChanged = new(); public sealed record class OpContentKVDataChange(uint[] Value) : WorldState.Operation { + public readonly uint[] Value = Value; protected override void Exec(WorldState ws) { ws.Client.ContentKeyValueData = Value; From 8e00fbd9a8fc5f1e67c45d4cb2607058333fb39c Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Sat, 8 Feb 2025 13:11:59 -0500 Subject: [PATCH 04/15] (skeleton) modules for hydatos fates --- BossMod/BossModule/AIHintsBuilder.cs | 2 +- BossMod/Modules/Stormblood/Foray/NM/Barong.cs | 18 ++++ BossMod/Modules/Stormblood/Foray/NM/Ceto.cs | 51 ++++++++++++ BossMod/Modules/Stormblood/Foray/NM/Daphne.cs | 42 ++++++++++ .../Modules/Stormblood/Foray/NM/Frostmane.cs | 19 +++++ .../Stormblood/Foray/NM/KingGoldemar.cs | 18 ++++ BossMod/Modules/Stormblood/Foray/NM/Leuke.cs | 18 ++++ BossMod/Modules/Stormblood/Foray/NM/Molech.cs | 19 +++++ BossMod/Modules/Stormblood/Foray/NM/Ovni.cs | 78 ++++++++++++++++++ .../Stormblood/Foray/NM/ProvenanceWatcher.cs | 19 +++++ .../Pathfinding/ObstacleMaps/827.639.1.bmp | Bin 0 -> 227566 bytes BossMod/Pathfinding/ObstacleMaps/maplist.json | 15 ++++ 12 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 BossMod/Modules/Stormblood/Foray/NM/Barong.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/Ceto.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/Daphne.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/Frostmane.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/KingGoldemar.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/Leuke.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/Molech.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/Ovni.cs create mode 100644 BossMod/Modules/Stormblood/Foray/NM/ProvenanceWatcher.cs create mode 100644 BossMod/Pathfinding/ObstacleMaps/827.639.1.bmp diff --git a/BossMod/BossModule/AIHintsBuilder.cs b/BossMod/BossModule/AIHintsBuilder.cs index 7277df63a1..23d24ec831 100644 --- a/BossMod/BossModule/AIHintsBuilder.cs +++ b/BossMod/BossModule/AIHintsBuilder.cs @@ -84,7 +84,7 @@ private void FillEnemies(AIHints hints, bool playerIsDefaultTank) private void CalculateAutoHints(AIHints hints, Actor player) { - var inFate = _ws.Client.ActiveFate.ID != 0 && player.Level <= Service.LuminaRow(_ws.Client.ActiveFate.ID)?.ClassJobLevelMax; + var inFate = Utils.IsPlayerSyncedToFate(_ws); var center = inFate ? _ws.Client.ActiveFate.Center : player.PosRot.XYZ(); var (e, bitmap) = Obstacles.Find(center); var resolution = bitmap?.PixelSize ?? 0.5f; diff --git a/BossMod/Modules/Stormblood/Foray/NM/Barong.cs b/BossMod/Modules/Stormblood/Foray/NM/Barong.cs new file mode 100644 index 0000000000..db1f05468f --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/Barong.cs @@ -0,0 +1,18 @@ +//namespace BossMod.Stormblood.Foray.NM.Barong; +//public enum OID : uint +//{ +// Boss = 0x2746, +// Helper = 0x233C, +//} + +//class BarongStates : StateMachineBuilder +//{ +// public BarongStates(BossModule module) : base(module) +// { +// TrivialPhase(); +// } +//} + +//[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 7965)] +//public class Barong(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsCircle(20)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs new file mode 100644 index 0000000000..f5a270b66a --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs @@ -0,0 +1,51 @@ +namespace BossMod.Stormblood.Foray.NM.Ceto; + +public enum OID : uint +{ + Boss = 0x2765, // R5.000, x1 + FaithlessGuard = 0x2767, // R2.000, x0 (spawn during fight) + LifelessSlave1 = 0x2766, // R2.700, x1 + LifelessSlave2 = 0x2785, // R2.700, x1 + LifelessSlave3 = 0x2784, // R2.700, x1 +} + +public enum AID : uint +{ + SickleStrike = 15466, // Boss->player, 3.5s cast, single-target + PetrifactionBoss = 15469, // Boss->self, 450 circle + AbyssalReaper = 15468, // Boss->self, 4.0s cast, range 18 circle + PetrifactionAdds = 15475, // LifelessSlave1/LifelessSlave2/LifelessSlave3->self, 4.0s cast, range 50 circle + CircleOfFlames = 15472, // FaithlessGuard->location, 3.0s cast, range 5 circle + TailSlap = 15471, // FaithlessGuard->self, 3.0s cast, range 12 120-degree cone + Petrattraction = 15473, // FaithlessGuard->2783, 3.0s cast, single-target + CircleBlade = 15470, // FaithlessGuard->self, 3.0s cast, range 7 circle +} + +class SickleStrike(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.SickleStrike)); +class PetrifactionBoss(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.PetrifactionBoss), range: 50); +class PetrifactionAdds(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.PetrifactionAdds), range: 50); +class AbyssalReaper(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.AbyssalReaper), new AOEShapeCircle(18)); +class CircleOfFlames(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.CircleOfFlames), 5); +class TailSlap(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.TailSlap), new AOEShapeCone(12, 60.Degrees())); +class Petrattraction(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.Petrattraction), 50, kind: Kind.TowardsOrigin); +class Adds(BossModule module) : Components.Adds(module, (uint)OID.FaithlessGuard); + +class CetoStates : StateMachineBuilder +{ + public CetoStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } +} + +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 7955, Contributors = "xan")] +public class Ceto(WorldState ws, Actor primary) : BossModule(ws, primary, new(747.8959f, -878.8765f), new ArenaBoundsCircle(80, MapResolution: 1)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs new file mode 100644 index 0000000000..f74e1b2729 --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs @@ -0,0 +1,42 @@ +namespace BossMod.Stormblood.Foray.NM.Daphne; + +public enum OID : uint +{ + Boss = 0x2744, // R6.875, x1 + Tentacle = 0x276E, // R7.200, x0 (spawn during fight) + Helper1 = 0x276A, // R0.500, x0 (spawn during fight) + Helper2 = 0x276B, // R0.500, x0 (spawn during fight) +} + +public enum AID : uint +{ + SpellwindCast = 15031, // Boss->self, 4.0s cast, single-target + SpellwindAOE = 15032, // Helper1->location, no cast, range 40 circle + Upburst = 15025, // Tentacle->self, 3.5s cast, range 8 circle + RoilingReach = 15029, // Boss->self, 4.5s cast, range 32 width 7 cross + Wallop = 15027, // Tentacle->self, 4.0s cast, range 50 width 7 rect + ChillingGlare = 15030, // Boss->self, 4.0s cast, range 40 circle +} + +class Spellwind(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.SpellwindCast)); +class Upburst(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Upburst), new AOEShapeCircle(8)); +class RoilingReach(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.RoilingReach), new AOEShapeRect(32, 3.5f)); +class Wallop(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Wallop), new AOEShapeRect(50, 3.5f)); +class ChillingGlare(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.ChillingGlare)); + +class DaphneStates : StateMachineBuilder +{ + public DaphneStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } +} + +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 7967, Contributors = "xan")] +public class Daphne(WorldState ws, Actor primary) : BossModule(ws, primary, new(207.8475f, -736.8179f), new ArenaBoundsCircle(80, MapResolution: 1)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/Frostmane.cs b/BossMod/Modules/Stormblood/Foray/NM/Frostmane.cs new file mode 100644 index 0000000000..3742dc2003 --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/Frostmane.cs @@ -0,0 +1,19 @@ +//namespace BossMod.Stormblood.Foray.NM.Frostmane; + +//public enum OID : uint +//{ +// Boss = 0x2761, +// Helper = 0x233C, +//} + +//class FrostmaneStates : StateMachineBuilder +//{ +// public FrostmaneStates(BossModule module) : base(module) +// { +// TrivialPhase(); +// } +//} + +//[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 8072)] +//public class Frostmane(WorldState ws, Actor primary) : BossModule(ws, primary, new(-681.4338f, -243.3619f), new ArenaBoundsCircle(80, MapResolution: 1)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/KingGoldemar.cs b/BossMod/Modules/Stormblood/Foray/NM/KingGoldemar.cs new file mode 100644 index 0000000000..02108f2e9a --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/KingGoldemar.cs @@ -0,0 +1,18 @@ +//namespace BossMod.Stormblood.Foray.NM.KingGoldemar; +//public enum OID : uint +//{ +// Boss = 0x2745, +// Helper = 0x233C, +//} + +//class KingGoldemarStates : StateMachineBuilder +//{ +// public KingGoldemarStates(BossModule module) : base(module) +// { +// TrivialPhase(); +// } +//} + +//[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 7966)] +//public class KingGoldemar(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsCircle(20)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/Leuke.cs b/BossMod/Modules/Stormblood/Foray/NM/Leuke.cs new file mode 100644 index 0000000000..35bbff52d8 --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/Leuke.cs @@ -0,0 +1,18 @@ +//namespace BossMod.Stormblood.Foray.NM.Leuke; +//public enum OID : uint +//{ +// Boss = 0x273C, +// Helper = 0x233C, +//} + +//class LeukeStates : StateMachineBuilder +//{ +// public LeukeStates(BossModule module) : base(module) +// { +// TrivialPhase(); +// } +//} + +//[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 8068)] +//public class Leuke(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsCircle(20)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/Molech.cs b/BossMod/Modules/Stormblood/Foray/NM/Molech.cs new file mode 100644 index 0000000000..dd0293b6d7 --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/Molech.cs @@ -0,0 +1,19 @@ +//namespace BossMod.Stormblood.Foray.NM.Molech; + +//public enum OID : uint +//{ +// Boss = 0x275D, +// Helper = 0x233C, +//} + +//class MolechStates : StateMachineBuilder +//{ +// public MolechStates(BossModule module) : base(module) +// { +// TrivialPhase(); +// } +//} + +//[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 8070)] +//public class Molech(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsCircle(20)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs new file mode 100644 index 0000000000..d79491815f --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs @@ -0,0 +1,78 @@ +namespace BossMod.Stormblood.Foray.NM.Ovni; + +public enum OID : uint +{ + Boss = 0x2685, // R16.000, x1 +} + +public enum AID : uint +{ + RockHard = 14786, // Boss->location, 3.0s cast, range 8 circle + TorrentialTorment = 14785, // Boss->self, 5.0s cast, range 40+R 45-degree cone + Fluorescence = 14789, // Boss->self, 3.0s cast, single-target + PullOfTheVoid = 14782, // Boss->self, 3.0s cast, range 30 circle + Megastorm = 14784, // Boss->self, 5.0s cast, range ?-40 donut + IonShower = 14787, // Boss->player, 5.0s cast, single-target + IonStorm = 14788, // Boss->players, no cast, range 20 circle + VitriolicBarrage = 14790, // Boss->self, 4.0s cast, range 25+R circle + ConcussiveOscillation = 14783, // Boss->self, 5.0s cast, range 24 circle +} + +public enum IconID : uint +{ + IonShower = 111, // player->self +} + +class PullOfTheVoid(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.PullOfTheVoid), 30, kind: Kind.TowardsOrigin, minDistanceBetweenHitboxes: true); +class Megastorm(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Megastorm), new AOEShapeDonut(4, 40)); +class ConcussiveOscillation(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ConcussiveOscillation), new AOEShapeCircle(24)); +class VitriolicBarrage(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.VitriolicBarrage)); +class RockHard(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.RockHard), 8); +class TorrentialTorment(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.TorrentialTorment), new AOEShapeCone(56, 22.5f.Degrees())); +class IonShower(BossModule module) : Components.GenericStackSpread(module, alwaysShowSpreads: true, raidwideOnResolve: false) +{ + private int _numCasts; + + public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) + { + if (iconID == (uint)IconID.IonShower && WorldState.Actors.Find(targetID) is { } target) + { + _numCasts = 0; + Spreads.Add(new(target, 20, WorldState.FutureTime(5))); + } + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.IonStorm && ++_numCasts >= 3) + Spreads.Clear(); + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + if (Spreads.Any(s => s.Target == actor)) + // just gtfo from boss as far as possible + hints.GoalZones.Add(p => (p - Module.PrimaryActor.Position).LengthSq() > 1600 ? 100 : 0); + else + base.AddAIHints(slot, actor, assignment, hints); + } +} + +class OvniStates : StateMachineBuilder +{ + public OvniStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } +} + +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 8060, Contributors = "xan")] +public class Ovni(WorldState ws, Actor primary) : BossModule(ws, primary, new(266.1068f, -97.09414f), new ArenaBoundsCircle(80, MapResolution: 1)); + diff --git a/BossMod/Modules/Stormblood/Foray/NM/ProvenanceWatcher.cs b/BossMod/Modules/Stormblood/Foray/NM/ProvenanceWatcher.cs new file mode 100644 index 0000000000..e1732e80ee --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/NM/ProvenanceWatcher.cs @@ -0,0 +1,19 @@ +//namespace BossMod.Stormblood.Foray.NM.ProvenanceWatcher; + +//public enum OID : uint +//{ +// Boss = 0x2686, +// Helper = 0x233C, +//} + +//class ProvenanceWatcherStates : StateMachineBuilder +//{ +// public ProvenanceWatcherStates(BossModule module) : base(module) +// { +// TrivialPhase(); +// } +//} + +//[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 8061)] +//public class ProvenanceWatcher(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsCircle(20)); + diff --git a/BossMod/Pathfinding/ObstacleMaps/827.639.1.bmp b/BossMod/Pathfinding/ObstacleMaps/827.639.1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e23cc9ba231c25c81c7f079aaa2388520feb4e65 GIT binary patch literal 227566 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 Date: Sun, 9 Feb 2025 00:49:18 -0500 Subject: [PATCH 05/15] eureka NM fixes --- BossMod/Modules/Stormblood/Foray/NM/Ceto.cs | 2 ++ BossMod/Modules/Stormblood/Foray/NM/Ovni.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs index f5a270b66a..b9be6eea7d 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs @@ -28,6 +28,7 @@ class PetrifactionAdds(BossModule module) : Components.CastGaze(module, ActionID class CircleOfFlames(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.CircleOfFlames), 5); class TailSlap(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.TailSlap), new AOEShapeCone(12, 60.Degrees())); class Petrattraction(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.Petrattraction), 50, kind: Kind.TowardsOrigin); +class CircleBlade(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.CircleBlade), new AOEShapeCircle(7)); class Adds(BossModule module) : Components.Adds(module, (uint)OID.FaithlessGuard); class CetoStates : StateMachineBuilder @@ -42,6 +43,7 @@ public CetoStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter(); } } diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs index d79491815f..0b353e10de 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs @@ -24,7 +24,7 @@ public enum IconID : uint } class PullOfTheVoid(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.PullOfTheVoid), 30, kind: Kind.TowardsOrigin, minDistanceBetweenHitboxes: true); -class Megastorm(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Megastorm), new AOEShapeDonut(4, 40)); +class Megastorm(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Megastorm), new AOEShapeDonut(5, 40)); class ConcussiveOscillation(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ConcussiveOscillation), new AOEShapeCircle(24)); class VitriolicBarrage(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.VitriolicBarrage)); class RockHard(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.RockHard), 8); From d07de645ffa2456ab6b4e06aab19d4b508f962c3 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:02:37 -0500 Subject: [PATCH 06/15] re-add caster instanceID to forbidden zones --- BossMod/BossModule/AIHintsBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BossMod/BossModule/AIHintsBuilder.cs b/BossMod/BossModule/AIHintsBuilder.cs index 23d24ec831..94deae8d8b 100644 --- a/BossMod/BossModule/AIHintsBuilder.cs +++ b/BossMod/BossModule/AIHintsBuilder.cs @@ -139,18 +139,18 @@ private void CalculateAutoHints(AIHints hints, Actor player) var finishAt = _ws.FutureTime(aoe.Caster.CastInfo.NPCRemainingTime); if (aoe.IsCharge) { - hints.AddForbiddenZone(ShapeDistance.Rect(aoe.Caster.Position, target, ((AOEShapeRect)aoe.Shape).HalfWidth), finishAt); + hints.AddForbiddenZone(ShapeDistance.Rect(aoe.Caster.Position, target, ((AOEShapeRect)aoe.Shape).HalfWidth), finishAt, aoe.Caster.InstanceID); } else if (aoe.Shape is AOEShapeCone cone) { // not sure how best to adjust cone shape distance to account for quantization error - we just pretend it is being cast from MaxError units "behind" the reported position and increase radius similarly var adjustedSourcePos = target + rot.ToDirection() * -MaxError; var adjustedRadius = cone.Radius + MaxError * 2; - hints.AddForbiddenZone(ShapeDistance.Cone(adjustedSourcePos, adjustedRadius, rot, cone.HalfAngle), finishAt); + hints.AddForbiddenZone(ShapeDistance.Cone(adjustedSourcePos, adjustedRadius, rot, cone.HalfAngle), finishAt, aoe.Caster.InstanceID); } else { - hints.AddForbiddenZone(aoe.Shape, target, rot, finishAt); + hints.AddForbiddenZone(aoe.Shape, target, rot, finishAt, aoe.Caster.InstanceID); } } } From b71590009658a601a42c16c3e2e43ae43f978495 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Sun, 9 Feb 2025 12:25:22 -0500 Subject: [PATCH 07/15] missed daphne cross aoe --- BossMod/Modules/Stormblood/Foray/NM/Daphne.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs index f74e1b2729..eac8a65344 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs @@ -20,7 +20,7 @@ public enum AID : uint class Spellwind(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.SpellwindCast)); class Upburst(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Upburst), new AOEShapeCircle(8)); -class RoilingReach(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.RoilingReach), new AOEShapeRect(32, 3.5f)); +class RoilingReach(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.RoilingReach), new AOEShapeCross(32, 3.5f)); class Wallop(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Wallop), new AOEShapeRect(50, 3.5f)); class ChillingGlare(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.ChillingGlare)); From 535ef1657a276e9591b152a1ad504ff8386be62a Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Sun, 9 Feb 2025 18:36:02 -0500 Subject: [PATCH 08/15] fixed utopian sky frfr --- BossMod/Modules/Dawntrail/Ultimate/FRU/P1UtopianSky.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BossMod/Modules/Dawntrail/Ultimate/FRU/P1UtopianSky.cs b/BossMod/Modules/Dawntrail/Ultimate/FRU/P1UtopianSky.cs index 143cc15c3b..21d9aaee1c 100644 --- a/BossMod/Modules/Dawntrail/Ultimate/FRU/P1UtopianSky.cs +++ b/BossMod/Modules/Dawntrail/Ultimate/FRU/P1UtopianSky.cs @@ -23,7 +23,7 @@ public override void OnActorModelStateChange(Actor actor, byte modelState, byte { Activation = WorldState.FutureTime(9.1f); AOEs.Add(new(_shape, actor.Position, actor.Rotation, Activation)); - DangerousSpots.Set((int)MathF.Round((Angle.FromDirection(actor.Position - Module.Center).Deg + 180) / 45) % 8); + DangerousSpots.Set((int)MathF.Round((-Angle.FromDirection(actor.Position - Module.Center).Deg + 180) / 45) % 8); } } @@ -172,7 +172,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (_spreadStack?.Stacks.Count > 0) spreadSpot &= 4; // stack on close spot - var direction = (~_seenDangerSpot).LowestSetBit() * 45.Degrees() + (spreadSpot >= 4 ? 0 : 180).Degrees(); + var direction = (4 - (~_seenDangerSpot).LowestSetBit()) % 4 * 45.Degrees() + (spreadSpot >= 4 ? 0 : 180).Degrees(); spreadSpot &= 3; direction += spreadSpot switch { From 0d5255189ac95a27fa99b9f4be6daa911013d809 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Sun, 9 Feb 2025 18:36:44 -0500 Subject: [PATCH 09/15] mnk downtime stuff --- BossMod/Autorotation/Standard/xan/Basexan.cs | 4 +- .../Autorotation/Standard/xan/Melee/MNK.cs | 122 ++++++++++++------ 2 files changed, 81 insertions(+), 45 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/Basexan.cs b/BossMod/Autorotation/Standard/xan/Basexan.cs index 77f1523cad..6f9b833dfe 100644 --- a/BossMod/Autorotation/Standard/xan/Basexan.cs +++ b/BossMod/Autorotation/Standard/xan/Basexan.cs @@ -515,9 +515,9 @@ public static RotationModuleDefinition DefineSharedTA(this RotationModuleDefinit return def; } - public static RotationModuleDefinition.ConfigRef DefineSimple(this RotationModuleDefinition def, Index track, string name, int minLevel = 1) where Index : Enum + public static RotationModuleDefinition.ConfigRef DefineSimple(this RotationModuleDefinition def, Index track, string name, int minLevel = 1, float uiPriority = 0) where Index : Enum { - return def.Define(track).As(name) + return def.Define(track).As(name, uiPriority: uiPriority) .AddOption(OffensiveStrategy.Automatic, "Auto", "Use when optimal", minLevel: minLevel) .AddOption(OffensiveStrategy.Delay, "Delay", "Don't use", minLevel: minLevel) .AddOption(OffensiveStrategy.Force, "Force", "Use ASAP", minLevel: minLevel); diff --git a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs index 48fa9b7b7b..8e2927474e 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs @@ -6,7 +6,7 @@ namespace BossMod.Autorotation.xan; public sealed class MNK(RotationModuleManager manager, Actor player) : Attackxan(manager, player) { - public enum Track { Potion = SharedTrack.Buffs, SSS, Meditation, FormShift, FiresReply, Nadi, RoF, RoW, PB, BH, TC, Blitz, Engage, TN } + public enum Track { BH = SharedTrack.Buffs, RoF, FiresReply, RoW, WindsReply, PB, Nadi, Blitz, SSS, FormShift, Meditation, TC, Potion, Engage, TN, Positional } public enum PotionStrategy { Manual, @@ -27,6 +27,12 @@ public enum FRStrategy Force, Delay } + public enum WRStrategy + { + Automatic, + Force, + PreDowntime + } public enum NadiStrategy { Automatic, @@ -62,6 +68,11 @@ public enum TCStrategy None, GapClose } + public enum PositionalStrategy + { + Automatic, + Ignore + } public enum BlitzStrategy { Automatic, @@ -83,44 +94,33 @@ public static RotationModuleDefinition Definition() var def = new RotationModuleDefinition("xan MNK", "Monk", "Standard rotation (xan)|Melee", "xan", RotationModuleQuality.Good, BitMask.Build(Class.MNK, Class.PGL), 100); def.DefineSharedTA(); - def.Define(Track.Potion).As("Pot") - .AddOption(PotionStrategy.Manual, "Do not automatically use") - .AddOption(PotionStrategy.PreBuffs, "Use ~4 GCDs before raid buff window") - .AddOption(PotionStrategy.Now, "Use ASAP"); - - def.DefineSimple(Track.SSS, "SixSidedStar", minLevel: 80).AddAssociatedActions(AID.SixSidedStar); - - def.Define(Track.Meditation).As("Meditate") - .AddOption(MeditationStrategy.Safe, "Use out of combat, during countdown, or if no enemies are targetable") - .AddOption(MeditationStrategy.Greedy, "Allow using when primary enemy is targetable, but out of range") - .AddOption(MeditationStrategy.Force, "Use even if enemy is in melee range") - .AddOption(MeditationStrategy.Delay, "Do not use") - .AddAssociatedActions(AID.SteeledMeditation); - def.DefineSimple(Track.FormShift, "FormShift", minLevel: 52).AddAssociatedActions(AID.FormShift); + // buffs + def.DefineSimple(Track.BH, "BH", minLevel: 70, uiPriority: 99).AddAssociatedActions(AID.Brotherhood); - def.Define(Track.FiresReply).As("FiresReply") + def.Define(Track.RoF).As("RoF", uiPriority: 96) + .AddOption(RoFStrategy.Automatic, "Auto", "Automatically use RoF during burst window", minLevel: 68) + .AddOption(RoFStrategy.Force, "Force", "Use ASAP", minLevel: 68) + .AddOption(RoFStrategy.ForceMidWeave, "ForceMid", "Use ASAP, but retain late-weave to ensure maximum GCDs covered", minLevel: 68) + .AddOption(RoFStrategy.Delay, "Delay", "Do not use", minLevel: 68) + .AddAssociatedActions(AID.RiddleOfFire); + def.Define(Track.FiresReply).As("FiresReply", uiPriority: 95) .AddOption(FRStrategy.Automatic, "Use after Opo GCD", minLevel: 100) .AddOption(FRStrategy.Ranged, "Use when out of melee range, or if about to expire", minLevel: 100) .AddOption(FRStrategy.Force, "Use ASAP", minLevel: 100) .AddOption(FRStrategy.Delay, "Do not use", minLevel: 100) .AddAssociatedActions(AID.FiresReply); - def.Define(Track.Nadi).As("Nadi") - .AddOption(NadiStrategy.Automatic, "Automatically choose best nadi (double lunar opener, otherwise alternate)", minLevel: 60) - .AddOption(NadiStrategy.Lunar, "Lunar", minLevel: 60) - .AddOption(NadiStrategy.Solar, "Solar", minLevel: 60); - - def.Define(Track.RoF).As("RoF") - .AddOption(RoFStrategy.Automatic, "Auto", "Automatically use RoF during burst window", minLevel: 68) - .AddOption(RoFStrategy.Force, "Force", "Use ASAP", minLevel: 68) - .AddOption(RoFStrategy.ForceMidWeave, "ForceMid", "Use ASAP, but retain late-weave to ensure maximum GCDs covered", minLevel: 68) - .AddOption(RoFStrategy.Delay, "Delay", "Do not use", minLevel: 68) - .AddAssociatedActions(AID.RiddleOfFire); + def.DefineSimple(Track.RoW, "RoW", minLevel: 72, uiPriority: 94).AddAssociatedActions(AID.RiddleOfWind); + def.Define(Track.WindsReply).As("WindsReply", uiPriority: 93) + .AddOption(WRStrategy.Automatic, "Use out of melee range, or if about to expire", minLevel: 96) + .AddOption(WRStrategy.Force, "Use ASAP", minLevel: 96) + .AddOption(WRStrategy.PreDowntime, "Ensure usage at least 2 GCDs before next downtime", minLevel: 96) + .AddAssociatedActions(AID.WindsReply); - def.DefineSimple(Track.RoW, "RoW", minLevel: 72).AddAssociatedActions(AID.RiddleOfWind); - def.Define(Track.PB).As("PB") + // PB-related settings + def.Define(Track.PB).As("PB", uiPriority: 89) .AddOption(PBStrategy.Automatic, "Automatically use after Opo before or during Riddle of Fire", minLevel: 50) .AddOption(PBStrategy.ForceOpo, "Use ASAP after next Opo", minLevel: 50) .AddOption(PBStrategy.Force, "Use ASAP", minLevel: 50) @@ -128,14 +128,11 @@ public static RotationModuleDefinition Definition() .AddOption(PBStrategy.DowntimeSolar, "Downtime prep: Solar", minLevel: 60, effect: 39) .AddOption(PBStrategy.DowntimeLunar, "Downtime prep: Lunar", minLevel: 60, effect: 39) .AddAssociatedActions(AID.PerfectBalance); - - def.DefineSimple(Track.BH, "BH", minLevel: 70).AddAssociatedActions(AID.Brotherhood); - def.Define(Track.TC).As("TC") - .AddOption(TCStrategy.None, "Do not use", minLevel: 35) - .AddOption(TCStrategy.GapClose, "Use if outside melee range", minLevel: 35) - .AddAssociatedActions(AID.Thunderclap); - - def.Define(Track.Blitz).As("Blitz") + def.Define(Track.Nadi).As("Nadi", uiPriority: 88) + .AddOption(NadiStrategy.Automatic, "Automatically choose best nadi (double lunar opener, otherwise alternate)", minLevel: 60) + .AddOption(NadiStrategy.Lunar, "Lunar", minLevel: 60) + .AddOption(NadiStrategy.Solar, "Solar", minLevel: 60); + def.Define(Track.Blitz).As("Blitz", uiPriority: 87) .AddOption(BlitzStrategy.Automatic, "Use ASAP", minLevel: 60) .AddOption(BlitzStrategy.RoF, "Hold blitz until Riddle of Fire is active", minLevel: 60) .AddOption(BlitzStrategy.Multi, "Hold blitz until at least two targets will be hit", minLevel: 60) @@ -143,13 +140,38 @@ public static RotationModuleDefinition Definition() .AddOption(BlitzStrategy.Delay, "Do not use", minLevel: 60) .AddAssociatedActions(AID.ElixirField, AID.FlintStrike, AID.TornadoKick, AID.ElixirBurst, AID.RisingPhoenix, AID.PhantomRush); - def.Define(Track.Engage).As("Engage") + // downtime stuff + def.DefineSimple(Track.SSS, "SixSidedStar", minLevel: 80, uiPriority: 79).AddAssociatedActions(AID.SixSidedStar); + def.DefineSimple(Track.FormShift, "FormShift", minLevel: 52, uiPriority: 78).AddAssociatedActions(AID.FormShift); + def.Define(Track.Meditation).As("Meditate", uiPriority: 77) + .AddOption(MeditationStrategy.Safe, "Use out of combat, during countdown, or if no enemies are targetable") + .AddOption(MeditationStrategy.Greedy, "Allow using when primary enemy is targetable, but out of range") + .AddOption(MeditationStrategy.Force, "Use even if enemy is in melee range") + .AddOption(MeditationStrategy.Delay, "Do not use") + .AddAssociatedActions(AID.SteeledMeditation); + + // other utils + def.Define(Track.TC).As("TC", uiPriority: 69) + .AddOption(TCStrategy.None, "Do not use", minLevel: 35) + .AddOption(TCStrategy.GapClose, "Use if outside melee range", minLevel: 35) + .AddAssociatedActions(AID.Thunderclap); + + def.Define(Track.Potion).As("Pot", uiPriority: 59) + .AddOption(PotionStrategy.Manual, "Do not automatically use") + .AddOption(PotionStrategy.PreBuffs, "Use ~4 GCDs before raid buff window") + .AddOption(PotionStrategy.Now, "Use ASAP"); + + def.Define(Track.Engage).As("Engage", uiPriority: 49) .AddOption(EngageStrategy.TC, "Thunderclap to target") .AddOption(EngageStrategy.Sprint, "Sprint to melee range") .AddOption(EngageStrategy.FacepullDK, "Precast Dragon Kick from melee range") .AddOption(EngageStrategy.FacepullDemo, "Precast Demolish from melee range"); - def.DefineSimple(Track.TN, "TrueNorth", minLevel: 50).AddAssociatedActions(AID.TrueNorth); + def.DefineSimple(Track.TN, "TrueNorth", minLevel: 50, uiPriority: 48).AddAssociatedActions(AID.TrueNorth); + def.Define(Track.Positional).As("Pos (AI)", uiPriority: 45) + .AddOption(PositionalStrategy.Automatic, "Tell AI mode to navigate to hit positionals") + .AddOption(PositionalStrategy.Ignore, "Tell AI mode to ignore positionals") + .AddAssociatedActions(AID.Demolish, AID.SnapPunch, AID.PouncingCoeurl); return def; } @@ -191,6 +213,8 @@ public enum Form { None, OpoOpo, Raptor, Coeurl } protected override float GetCastTime(AID aid) => 0; + public float EffectiveDowntimeIn => MathF.Max(0, DowntimeIn - GetApplicationDelay(AID.SixSidedStar)); + private (AID action, bool isTargeted) GetCurrentBlitz() { if (BeastCount != 3) @@ -344,7 +368,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) UseBlitz(strategy, currentBlitz); FiresReply(strategy); - WindsReply(); + WindsReply(strategy); if (UseAOE) { @@ -379,14 +403,15 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) PushGCD(AID.SixSidedStar, primaryTarget, GCDPriority.SSS); break; case OffensiveStrategy.Automatic: - if (DowntimeIn > 0 && !CanFitGCD(DowntimeIn - GetApplicationDelay(AID.SixSidedStar), 1)) + if (EffectiveDowntimeIn > 0 && !CanFitGCD(EffectiveDowntimeIn, 1)) PushGCD(AID.SixSidedStar, primaryTarget, GCDPriority.SSS); break; } Prep(strategy); - var pos = NextPositional; + var pos = strategy.Option(Track.Positional).As() == PositionalStrategy.Automatic ? NextPositional : (Positional.Any, false); + UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.ArmOfTheDestroyer, AOEBreakpoint, positional: pos.Item1, maximumActionRange: 20); @@ -664,7 +689,7 @@ private void FiresReply(StrategyValues strategy) PushGCD(AID.FiresReply, BestRangedTarget, prio); } - private void WindsReply() + private void WindsReply(StrategyValues strategy) { if (WindsReplyLeft <= GCD) return; @@ -676,6 +701,17 @@ private void WindsReply() if (FireLeft > GCD && !CanFitGCD(FireLeft, 1) || !CanFitGCD(WindsReplyLeft, 1)) prio = GCDPriority.WindsReply; + switch (strategy.Option(Track.WindsReply).As()) + { + case WRStrategy.Force: + prio = GCDPriority.WindsReply; + break; + case WRStrategy.PreDowntime: + if (EffectiveDowntimeIn < WindsReplyLeft && !CanFitGCD(EffectiveDowntimeIn, 2)) + prio = GCDPriority.WindsReply; + break; + } + PushGCD(AID.WindsReply, BestLineTarget, prio); } From b5e4c0fb8a0f39f5c42a5cb583ea3f53681b7c43 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:14:39 -0500 Subject: [PATCH 10/15] add "spikes" state to Enemy --- BossMod/ActionTweaks/AutoAutosTweak.cs | 4 +++- BossMod/BossModule/AIHints.cs | 1 + .../Endwalker/DeepDungeon/EurekaOrthos/EOFloorModule.cs | 4 ++-- BossMod/Modules/Global/DeepDungeon/AutoClear.cs | 8 ++++---- .../DeepDungeon/PalaceOfTheDead/PalaceFloorModule.cs | 5 ++--- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/BossMod/ActionTweaks/AutoAutosTweak.cs b/BossMod/ActionTweaks/AutoAutosTweak.cs index 267b3ebc70..5c9fee9b43 100644 --- a/BossMod/ActionTweaks/AutoAutosTweak.cs +++ b/BossMod/ActionTweaks/AutoAutosTweak.cs @@ -33,7 +33,9 @@ public bool GetDesiredState(bool currentState, ulong targetId) if (_config.PyreticThreshold > 0 && hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && hints.ImminentSpecialMode.activation < ws.FutureTime(_config.PyreticThreshold)) return false; // pyretic => disable autos - if (hints.FindEnemy(target)?.Priority == AIHints.Enemy.PriorityForbidden) + var enemy = hints.FindEnemy(target); + + if (enemy?.Priority == AIHints.Enemy.PriorityForbidden || enemy?.Spikes == true) return false; return player.InCombat || ws.Client.CountdownRemaining <= PrePullThreshold; // no reason not to enable autos! diff --git a/BossMod/BossModule/AIHints.cs b/BossMod/BossModule/AIHints.cs index 674903e111..3572ae5313 100644 --- a/BossMod/BossModule/AIHints.cs +++ b/BossMod/BossModule/AIHints.cs @@ -23,6 +23,7 @@ public class Enemy(Actor actor, int priority, bool shouldBeTanked) public bool ShouldBeInterrupted; // if set and enemy is casting interruptible spell, some ranged/tank will try to interrupt public bool ShouldBeStunned; // if set, AI will stun if possible public bool StayAtLongRange; // if set, players with ranged attacks don't bother coming closer than max range (TODO: reconsider) + public bool Spikes; // if set, autoattacks will be prevented } public enum SpecialMode diff --git a/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/EOFloorModule.cs b/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/EOFloorModule.cs index e069ca33ff..1a3da30811 100644 --- a/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/EOFloorModule.cs +++ b/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/EOFloorModule.cs @@ -170,7 +170,7 @@ protected override void OnCastFinished(Actor actor) // setting target to forbidden when it gains the spikes status is too late case AID.GelidCharge: case AID.SmolderingScales: - ForbiddenTargets.Add((actor, World.FutureTime(10))); + Spikes.Add((actor, World.FutureTime(10))); break; } } @@ -219,7 +219,7 @@ protected override void OnStatusLose(Actor actor, ActorStatus status) { case (uint)SID.IceSpikes: case (uint)SID.BlazeSpikes: - ForbiddenTargets.RemoveAll(t => t.Actor == actor); + Spikes.RemoveAll(t => t.Actor == actor); break; } } diff --git a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs index a4d0de13e6..1224370f08 100644 --- a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs +++ b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs @@ -55,7 +55,7 @@ public abstract class AutoClear : ZoneModule private readonly List Gazes = []; protected readonly List Interrupts = []; protected readonly List Stuns = []; - protected readonly List<(Actor Actor, DateTime Timeout)> ForbiddenTargets = []; + protected readonly List<(Actor Actor, DateTime Timeout)> Spikes = []; protected readonly List HintDisabled = []; private readonly List LOS = []; private readonly List IgnoreTraps = []; @@ -228,7 +228,7 @@ private void ClearState() Gazes.Clear(); Interrupts.Clear(); Stuns.Clear(); - ForbiddenTargets.Clear(); + Spikes.Clear(); HintDisabled.Clear(); LOS.Clear(); Walls.Clear(); @@ -621,10 +621,10 @@ private void DrawAOEs(int playerSlot, Actor player, AIHints hints) hints.AddForbiddenZone(new AOEShapeCircle(kb.Radius), kb.Source.Position, default, castFinish); }); - IterAndExpire(ForbiddenTargets, t => t.Timeout <= World.CurrentTime, t => + IterAndExpire(Spikes, t => t.Timeout <= World.CurrentTime, t => { if (hints.FindEnemy(t.Actor) is { } enemy) - enemy.Priority = AIHints.Enemy.PriorityForbidden; + enemy.Spikes = true; }); } diff --git a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/PalaceFloorModule.cs b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/PalaceFloorModule.cs index 57b2dacefe..8aa2214722 100644 --- a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/PalaceFloorModule.cs +++ b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/PalaceFloorModule.cs @@ -65,8 +65,7 @@ protected override void OnStatusGain(Actor actor, ActorStatus status) { case SID.BlazeSpikes: case SID.IceSpikes: - if (Palace.Floor > 60) - ForbiddenTargets.Add((actor, World.FutureTime(10))); + Spikes.Add((actor, World.FutureTime(10))); break; } } @@ -77,7 +76,7 @@ protected override void OnStatusLose(Actor actor, ActorStatus status) { case SID.BlazeSpikes: case SID.IceSpikes: - ForbiddenTargets.RemoveAll(t => t.Actor == actor); + Spikes.RemoveAll(t => t.Actor == actor); break; } } From 2e337074fe290e72c5012d9d106c94f8dc7ada76 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:42:44 -0500 Subject: [PATCH 11/15] use proper intersection test for AOE targets calculation --- BossMod/BossModule/AIHints.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/BossMod/BossModule/AIHints.cs b/BossMod/BossModule/AIHints.cs index 3572ae5313..180828d3a2 100644 --- a/BossMod/BossModule/AIHints.cs +++ b/BossMod/BossModule/AIHints.cs @@ -203,8 +203,13 @@ public void InitPathfindMap(Pathfinding.Map map) public int NumPriorityTargetsInAOECone(WPos origin, float radius, WDir direction, Angle halfAngle) => NumPriorityTargetsInAOE(a => TargetInAOECone(a.Actor, origin, radius, direction, halfAngle)); public int NumPriorityTargetsInAOERect(WPos origin, WDir direction, float lenFront, float halfWidth, float lenBack = 0) => NumPriorityTargetsInAOE(a => TargetInAOERect(a.Actor, origin, direction, lenFront, halfWidth, lenBack)); public bool TargetInAOECircle(Actor target, WPos origin, float radius) => target.Position.InCircle(origin, radius + target.HitboxRadius); - public bool TargetInAOECone(Actor target, WPos origin, float radius, WDir direction, Angle halfAngle) => target.Position.InCircleCone(origin, radius + target.HitboxRadius, direction, halfAngle); - public bool TargetInAOERect(Actor target, WPos origin, WDir direction, float lenFront, float halfWidth, float lenBack = 0) => target.Position.InRect(origin, direction, lenFront + target.HitboxRadius, lenBack, halfWidth); + public bool TargetInAOECone(Actor target, WPos origin, float radius, WDir direction, Angle halfAngle) => Intersect.CircleCone(target.Position, target.HitboxRadius, origin, radius, direction, halfAngle); + public bool TargetInAOERect(Actor target, WPos origin, WDir direction, float lenFront, float halfWidth, float lenBack = 0) + { + var rectCenterOffset = (lenFront - lenBack) / 2; + var rectCenter = origin + direction * rectCenterOffset; + return Intersect.CircleRect(target.Position, target.HitboxRadius, rectCenter, direction, halfWidth, (lenFront + lenBack) / 2); + } // goal zones // simple goal zone that returns 1 if target is in range, useful for single-target actions From faf31ac36a507ae9ba372ca392057ed046067788 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:46:07 -0500 Subject: [PATCH 12/15] DD stuff --- BossMod/Autorotation/MiscAI/AutoPull.cs | 2 +- .../Autorotation/Standard/xan/Ranged/MCH.cs | 2 +- BossMod/BossModule/ZoneModule.cs | 1 + BossMod/BossModule/ZoneModuleWindow.cs | 11 +++++++++- .../Modules/Global/DeepDungeon/AutoClear.cs | 19 ++++++++++++++++-- BossMod/Modules/Global/DeepDungeon/Config.cs | 3 +++ .../Pathfinding/ObstacleMaps/562.175.2.bmp | Bin 39662 -> 39662 bytes .../Pathfinding/ObstacleMaps/563.176.1.bmp | Bin 37934 -> 37934 bytes .../Pathfinding/ObstacleMaps/564.177.1.bmp | Bin 38870 -> 38870 bytes 9 files changed, 33 insertions(+), 5 deletions(-) diff --git a/BossMod/Autorotation/MiscAI/AutoPull.cs b/BossMod/Autorotation/MiscAI/AutoPull.cs index d99b20dac0..db0ebf8c24 100644 --- a/BossMod/Autorotation/MiscAI/AutoPull.cs +++ b/BossMod/Autorotation/MiscAI/AutoPull.cs @@ -13,7 +13,7 @@ public static RotationModuleDefinition Definition() 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"); + def.AbilityTrack(Track.Hunt, "Automatically attack hunt marks once they are below 95% HP"); return def; } diff --git a/BossMod/Autorotation/Standard/xan/Ranged/MCH.cs b/BossMod/Autorotation/Standard/xan/Ranged/MCH.cs index 9e5c6daa19..07ddd4b0dc 100644 --- a/BossMod/Autorotation/Standard/xan/Ranged/MCH.cs +++ b/BossMod/Autorotation/Standard/xan/Ranged/MCH.cs @@ -179,7 +179,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) private void OGCD(StrategyValues strategy, Enemy? primaryTarget) { - if (CountdownRemaining == null && !Player.InCombat && Player.DistanceToHitbox(primaryTarget) <= 25 && ReassembleLeft == 0 && !Overheated && AlwaysReassemble(NextGCD)) + if (CountdownRemaining == null && !Player.InCombat && Player.DistanceToHitbox(primaryTarget) <= 25 && ReassembleLeft == 0 && ShouldReassemble(strategy, primaryTarget)) PushGCD(AID.Reassemble, Player, priority: 50); if (!Player.InCombat || primaryTarget == null) diff --git a/BossMod/BossModule/ZoneModule.cs b/BossMod/BossModule/ZoneModule.cs index e468ae6ab5..981777bb50 100644 --- a/BossMod/BossModule/ZoneModule.cs +++ b/BossMod/BossModule/ZoneModule.cs @@ -23,6 +23,7 @@ public virtual void Update() { } public virtual void CalculateAIHints(int playerSlot, Actor player, AIHints hints) { } // note: this is called after framework automatically fills auto-detected hints public virtual bool WantDrawExtra() => false; // return true if it wants to draw something in a separate window public virtual void DrawExtra() { } + public virtual string WindowName() => ""; public void DrawGlobalHints() { diff --git a/BossMod/BossModule/ZoneModuleWindow.cs b/BossMod/BossModule/ZoneModuleWindow.cs index f696183827..a4b156b401 100644 --- a/BossMod/BossModule/ZoneModuleWindow.cs +++ b/BossMod/BossModule/ZoneModuleWindow.cs @@ -1,4 +1,6 @@ -namespace BossMod; +using Dalamud.Utility; + +namespace BossMod; public class ZoneModuleWindow : UIWindow { @@ -13,6 +15,13 @@ public class ZoneModuleWindow : UIWindow public override void PreOpenCheck() { IsOpen = _zmm.ActiveModule?.WantDrawExtra() ?? false; + if (IsOpen) + { + var title = _zmm.ActiveModule!.WindowName(); + if (title.IsNullOrEmpty()) + title = "Zone module###Zone module"; + WindowName = title; + } } public override void Draw() diff --git a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs index 1224370f08..f289e87b2a 100644 --- a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs +++ b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs @@ -293,6 +293,8 @@ private bool OpenSilver public override bool WantDrawExtra() => _config.EnableMinimap && !Palace.IsBossFloor; + public sealed override string WindowName() => "VBM DD minimap###Zone module"; + public override void DrawExtra() { var player = World.Party.Player(); @@ -445,6 +447,18 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint revealedTraps.Add(ShapeDistance.Circle(a.Position, 2)); } + var fullClear = false; + if (_config.FullClear) + { + var unexplored = Array.FindIndex(Palace.Rooms, d => (byte)d > 0 && !d.HasFlag(RoomFlags.Revealed)); + if (unexplored > 0) + { + DesiredRoom = unexplored; + fullClear = true; + } + } + + if (_config.TrapHints && _trapsHidden) { var traps = _trapsCurrentZone.Where(t => t.InCircle(player.Position, 30) && !IgnoreTraps.Any(b => b.AlmostEqual(t, 1))).Select(t => ShapeDistance.Circle(t, 2)).ToList(); @@ -479,9 +493,10 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint if (!player.InCombat && _config.AutoPassage && Palace.PassageActive) { - DesiredRoom = Array.FindIndex(Palace.Rooms, d => d.HasFlag(RoomFlags.Passage)); + if (DesiredRoom == 0) + DesiredRoom = Array.FindIndex(Palace.Rooms, d => d.HasFlag(RoomFlags.Passage)); - if (passage is Actor c) + if (passage is Actor c && !fullClear) { hints.GoalZones.Add(hints.GoalSingleTarget(c.Position, 2, 0.5f)); // give pathfinder a little help lmao diff --git a/BossMod/Modules/Global/DeepDungeon/Config.cs b/BossMod/Modules/Global/DeepDungeon/Config.cs index 7d197f6e1b..e126593501 100644 --- a/BossMod/Modules/Global/DeepDungeon/Config.cs +++ b/BossMod/Modules/Global/DeepDungeon/Config.cs @@ -42,4 +42,7 @@ public enum ClearBehavior public bool SilverCoffer = true; [PropertyDisplay("Open bronze coffers")] public bool BronzeCoffer = true; + + [PropertyDisplay("Reveal all rooms before proceeding to next floor")] + public bool FullClear = false; } diff --git a/BossMod/Pathfinding/ObstacleMaps/562.175.2.bmp b/BossMod/Pathfinding/ObstacleMaps/562.175.2.bmp index 17079651cb37d28e0966622e682a6afb1bb66266..1beb6798a4c44eaae3251f5b0f3f984ad45650ae 100644 GIT binary patch delta 251 zcmaF2mFeA9rVZ8xjQ=Ow7`Ow;7BG1ROo|wSL}CoxdH*y12Y~|&{C^l2CLc5to_yF) z11R?fsKS1-i4lmbfs*o*9~)@^`8>uT(K*K2lk1ITz$|wTdj_yM4<-xficJ1*tixo_ zFuBl2WU`zt8<5L7*~dg3Boyck5$ZPqs|fTKoh)U_J^8MQ8qgdTQ;@knrtZA<4D}%6 p7(l>oqM_*IT%e)@rqV#^J77gBW*`sbfXOXj@(+mI>|(wu835p*T~Po4 delta 254 zcmaF2mFeA9rVZ8xjQ=Lv7`QY3ncM;-e@s3DCPfV089z*pF?8pB!1x~o4lwZlVPKei z&`fypVM7hZ1C!qXRWM98F#?e_M(&eYa|9-L8wpH)Y@{)HDv&L0YyqZM8_P_t2V#)e zYN(imF3;ru#yU(u0}FjbCd=uv0lBP`eN5CrLV?~8p?(vvia>AC$x^1=lkb|S0j**& z1zG50>dwo+P!Dn#0|?knG!&hj3siK#R2nFK2dqfN4CJ32Ao*wV79ja=@*fbn*~NTS FG60r&TG9Xj diff --git a/BossMod/Pathfinding/ObstacleMaps/563.176.1.bmp b/BossMod/Pathfinding/ObstacleMaps/563.176.1.bmp index 9b5e685cfa98d0f756a8c1ee77b66b145150ff89..4657994c0fb55e2f280767cc4ca5168e6e0b863c 100644 GIT binary patch delta 237 zcmZ3tf@$3frVYt%lcn7_C+E1?Gd`HS#?2i_esObSVtz2$#vM$xK&UeiO2h*!xX?pp z@&ON)$u%BoAf*tl7J|t(na@)N#tra<%LI6`O`eC4d4P~9@Zy{NmXCe1t(PcFUx61~ rUxAn8WJzy9sLUj9u!N2`*exXxY7c~B@d0_k2TV=@k(=-M1i1qM83-lf;WjzRLu_(^ zhtcHI20WAVJvf1Ej>-Eyv?nKfh(g)ylleSVCSOe#0`m(zSSAN}YN7GjCeH(^f$6#L ziO`?##XtEiANyomFHyLBz8A8*)MQC-LAcz1FKv)}b-cmuErC#bAQX!a$VWavvSIQR L5V`q|PmntR793Ig diff --git a/BossMod/Pathfinding/ObstacleMaps/564.177.1.bmp b/BossMod/Pathfinding/ObstacleMaps/564.177.1.bmp index 59b311fa0a77ebcaed783f604faf67aece0f620e..92defcc0323b27b1a9cf25340d729c7bc93e2370 100644 GIT binary patch delta 128 zcmcb%p6S|prVYvZlcn|LCO7J9G1(uOyjI^0MA;e`PLAtlo2+BN58;AE*XnCe-e4dN z5;<$&2GqtsIm=LIvY#O@lx+!;dTVGo`JN#!gbCIM63H_Hi_{u{L^dBa5=;dEuFEcm delta 129 zcmcb%p6S|prVYvZObiT@W%XqyH|lGFm}~XjK$NY4;pDh(w#hmM{17f!bgjPj Date: Mon, 10 Feb 2025 20:05:31 -0500 Subject: [PATCH 13/15] fix ST heals when soloing --- .../Standard/xan/AI/TrackPartyHealth.cs | 2 +- BossMod/BossModule/AIHintsVisualizer.cs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/AI/TrackPartyHealth.cs b/BossMod/Autorotation/Standard/xan/AI/TrackPartyHealth.cs index 35bf656092..02bad91599 100644 --- a/BossMod/Autorotation/Standard/xan/AI/TrackPartyHealth.cs +++ b/BossMod/Autorotation/Standard/xan/AI/TrackPartyHealth.cs @@ -105,7 +105,7 @@ private PartyHealthState CalculatePartyHealthState(Func filter) private PartyHealthState CalcPartyHealthInArea(WPos center, float radius) => CalculatePartyHealthState(act => act.Position.InCircle(center, radius)); - public (Actor Target, PartyMemberState State)? BestSTHealTarget => PartyHealth.StdDev > AOEBreakpointHPVariance ? (World.Party[PartyHealth.LowestHPSlot]!, PartyMemberStates[PartyHealth.LowestHPSlot]) : null; + public (Actor Target, PartyMemberState State)? BestSTHealTarget => PartyHealth.StdDev > AOEBreakpointHPVariance || PartyHealth.Count == 1 ? (World.Party[PartyHealth.LowestHPSlot]!, PartyMemberStates[PartyHealth.LowestHPSlot]) : null; public bool ShouldHealInArea(WPos center, float radius, float hpThreshold) { diff --git a/BossMod/BossModule/AIHintsVisualizer.cs b/BossMod/BossModule/AIHintsVisualizer.cs index 9d41d634a2..2a644dec3d 100644 --- a/BossMod/BossModule/AIHintsVisualizer.cs +++ b/BossMod/BossModule/AIHintsVisualizer.cs @@ -1,4 +1,5 @@ -using BossMod.Pathfinding; +using BossMod.Autorotation.xan.AI; +using BossMod.Pathfinding; using ImGuiNET; namespace BossMod; @@ -9,9 +10,12 @@ public class AIHintsVisualizer(AIHints hints, WorldState ws, Actor player, float private MapVisualizer? _pathfindVisualizer; private readonly NavigationDecision.Context _naviCtx = new(); private NavigationDecision _navi; + private readonly TrackPartyHealth _partyHealth = new(ws); public void Draw(UITree tree) { + _partyHealth.Update(hints); + foreach (var _1 in tree.Node("Potential targets", hints.PotentialTargets.Count == 0)) { tree.LeafNodes(hints.PotentialTargets, e => $"[{e.Priority}] {e.Actor} (str={e.AttackStrength:f2}), dist={(e.Actor.Position - player.Position).Length():f2}, tank={e.ShouldBeTanked}/{e.PreferProvoking}/{e.DesiredPosition}/{e.DesiredRotation}"); @@ -38,6 +42,13 @@ public void Draw(UITree tree) { tree.LeafNodes(hints.PredictedDamage, d => $"[{string.Join(", ", ws.Party.WithSlot().IncludedInMask(d.players).Select(ia => ia.Item2.Name))}], at {Math.Max(0, (d.activation - ws.CurrentTime).TotalSeconds):f3}"); } + foreach (var _1 in tree.Node("Party health")) + { + var ph = _partyHealth.PartyHealth; + ImGui.TextUnformatted($"Total: {ph.Count}"); + ImGui.TextUnformatted($"Average: {ph.Avg * 100:f2} / stddev {ph.StdDev * 100:f2}"); + ImGui.TextUnformatted($"Lowest HP ally: {ws.Party[ph.LowestHPSlot]}"); + } foreach (var _1 in tree.Node("Planned actions", hints.ActionsToExecute.Entries.Count == 0)) { tree.LeafNodes(hints.ActionsToExecute.Entries, e => $"{e.Action} @ {e.Target} (priority {e.Priority})"); From f57ca5b635c244a9af267cf3d21e2b8860168199 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:39:57 -0500 Subject: [PATCH 14/15] actually navigate in combat --- BossMod/Modules/Global/DeepDungeon/AutoClear.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs index f289e87b2a..7b483879bb 100644 --- a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs +++ b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs @@ -685,14 +685,15 @@ private void HandleFloorPathfind(Actor player, AIHints hints) hints.GoalZones.Add(p => { var pp = player.Position; - return d switch + var improvement = d switch { Direction.North => pp.Z - p.Z, Direction.South => p.Z - pp.Z, Direction.East => p.X - pp.X, Direction.West => pp.X - p.X, _ => 0, - } * 0.001f; + }; + return improvement > 10 ? 10 : 0; }); } From 1e34ed3b01face4aba08524a4d5010dac21fa54c Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:44:03 -0500 Subject: [PATCH 15/15] add "since" attribute for new safe dash feature --- BossMod/ActionTweaks/ActionTweaksConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BossMod/ActionTweaks/ActionTweaksConfig.cs b/BossMod/ActionTweaks/ActionTweaksConfig.cs index 5b38395729..6ad6017e83 100644 --- a/BossMod/ActionTweaks/ActionTweaksConfig.cs +++ b/BossMod/ActionTweaks/ActionTweaksConfig.cs @@ -77,6 +77,6 @@ public enum GroundTargetingMode [PropertyDisplay("Automatic target selection for ground-targeted abilities")] public GroundTargetingMode GTMode = GroundTargetingMode.Manual; - [PropertyDisplay("Try to prevent dashing into AOEs", tooltip: "Prevent automatic use of damaging gap closers (like WAR Onslaught) if they would move you into a dangerous area. May not work as expected in instances that do not have modules.")] + [PropertyDisplay("Try to prevent dashing into AOEs", tooltip: "Prevent automatic use of damaging gap closers (like WAR Onslaught) if they would move you into a dangerous area. May not work as expected in instances that do not have modules.", since: "0.0.0.290")] public bool PreventDangerousDash = false; }