diff --git a/BossMod/ActionQueue/Melee/DRG.cs b/BossMod/ActionQueue/Melee/DRG.cs index 7267220a41..cd24f52948 100644 --- a/BossMod/ActionQueue/Melee/DRG.cs +++ b/BossMod/ActionQueue/Melee/DRG.cs @@ -93,6 +93,7 @@ public enum SID : uint Stun = 2, // applied by Leg Sweep to target NastrondReady = 3844, // applied by Geirskogul to self DragonsFlight = 3845, // applied by Dragonfire Dive to self + StarcrossReady = 3846, // applied by Stardiver to self } public sealed class Definitions : IDisposable diff --git a/BossMod/ActionQueue/Melee/RPR.cs b/BossMod/ActionQueue/Melee/RPR.cs index fc2f970670..99a560875a 100644 --- a/BossMod/ActionQueue/Melee/RPR.cs +++ b/BossMod/ActionQueue/Melee/RPR.cs @@ -5,50 +5,55 @@ public enum AID : uint None = 0, Sprint = ClassShared.AID.Sprint, - TheEnd = 24858, // LB3, 4.5s cast, range 8, single-target, targets=hostile, animLock=???, castAnimLock=3.700 - Slice = 24373, // L1, instant, GCD, range 3, single-target, targets=hostile - WaxingSlice = 24374, // L5, instant, GCD, range 3, single-target, targets=hostile - ShadowofDeath = 24378, // L10, instant, GCD, range 3, single-target, targets=hostile - Harpe = 24386, // L15, 1.3s cast, GCD, range 25, single-target, targets=hostile - HellsIngress = 24401, // L20, instant, 20.0s CD (group 3), range 0, single-target, targets=self, animLock=0.800 - HellsEgress = 24402, // L20, instant, 20.0s CD (group 3), range 0, single-target, targets=self, animLock=0.800 - SpinningScythe = 24376, // L25, instant, GCD, range 0, AOE 5 circle, targets=self - InfernalSlice = 24375, // L30, instant, GCD, range 3, single-target, targets=hostile - WhorlofDeath = 24379, // L35, instant, GCD, range 0, AOE 5 circle, targets=self - ArcaneCrest = 24404, // L40, instant, 30.0s CD (group 5), range 0, single-target, targets=self - NightmareScythe = 24377, // L45, instant, GCD, range 0, AOE 5 circle, targets=self - BloodStalk = 24389, // L50, instant, 1.0s CD (group 0), range 3, single-target, targets=hostile - GrimSwathe = 24392, // L55, instant, 1.0s CD (group 0), range 8, AOE 8+R ?-degree cone, targets=hostile - SoulSlice = 24380, // L60, instant, 30.0s CD (group 4/57), range 3, single-target, targets=hostile - SoulScythe = 24381, // L65, instant, 30.0s CD (group 4/57), range 0, AOE 5 circle, targets=self - UnveiledGibbet = 24390, // L70, instant, 1.0s CD (group 0), range 3, single-target, targets=hostile - Gibbet = 24382, // L70, instant, GCD, range 3, single-target, targets=hostile - UnveiledGallows = 24391, // L70, instant, 1.0s CD (group 0), range 3, single-target, targets=hostile - Gallows = 24383, // L70, instant, GCD, range 3, single-target, targets=hostile - Guillotine = 24384, // L70, instant, GCD, range 8, AOE 8+R ?-degree cone, targets=hostile - ArcaneCircle = 24405, // L72, instant, 120.0s CD (group 14), range 0, AOE 30 circle, targets=self - Regress = 24403, // L74, instant, 10.0s CD (group 1), range 30, ???, targets=area, animLock=0.800 - Gluttony = 24393, // L76, instant, 60.0s CD (group 9), range 25, AOE 5 circle, targets=hostile - Enshroud = 24394, // L80, instant, 15.0s CD (group 2), range 0, single-target, targets=self - VoidReaping = 24395, // L80, instant, GCD, range 3, single-target, targets=hostile - CrossReaping = 24396, // L80, instant, GCD, range 3, single-target, targets=hostile - GrimReaping = 24397, // L80, instant, GCD, range 8, AOE 8+R ?-degree cone, targets=hostile - HarvestMoon = 24388, // L82, instant, GCD, range 25, AOE 5 circle, targets=hostile - SoulSow = 24387, // L82, 5.0s cast, GCD, range 0, single-target, targets=self - LemuresSlice = 24399, // L86, instant, 1.0s CD (group 0), range 3, single-target, targets=hostile - LemuresScythe = 24400, // L86, instant, 1.0s CD (group 0), range 8, AOE 8+R ?-degree cone, targets=hostile - PlentifulHarvest = 24385, // L88, instant, GCD, range 15, AOE 15+R width 4 rect, targets=hostile - Communio = 24398, // L90, 1.3s cast, GCD, range 25, AOE 5 circle, targets=hostile, animLock=??? + TheEnd = 24858, // LB3, 4.5s cast, range 8, single-target, targets=Hostile, animLock=3.700s? + Slice = 24373, // L1, instant, GCD, range 3, single-target, targets=Hostile + WaxingSlice = 24374, // L5, instant, GCD, range 3, single-target, targets=Hostile + ShadowofDeath = 24378, // L10, instant, GCD, range 3, single-target, targets=Hostile + Harpe = 24386, // L15, 1.3s cast, GCD, range 25, single-target, targets=Hostile + HellsEgress = 24402, // L20, instant, 20.0s CD (group 4), range 0, single-target, targets=Self, animLock=0.800s? + HellsIngress = 24401, // L20, instant, 20.0s CD (group 4), range 0, single-target, targets=Self, animLock=0.800s? + SpinningScythe = 24376, // L25, instant, GCD, range 0, AOE 5 circle, targets=Self + InfernalSlice = 24375, // L30, instant, GCD, range 3, single-target, targets=Hostile + WhorlofDeath = 24379, // L35, instant, GCD, range 0, AOE 5 circle, targets=Self + ArcaneCrest = 24404, // L40, instant, 30.0s CD (group 6), range 0, single-target, targets=Self + NightmareScythe = 24377, // L45, instant, GCD, range 0, AOE 5 circle, targets=Self + BloodStalk = 24389, // L50, instant, 1.0s CD (group 0), range 3, single-target, targets=Hostile + GrimSwathe = 24392, // L55, instant, 1.0s CD (group 0), range 8, AOE 8+R ?-degree cone, targets=Hostile + SoulSlice = 24380, // L60, instant, 30.0s CD (group 8/57) (1-2 charges), range 3, single-target, targets=Hostile + SoulScythe = 24381, // L65, instant, 30.0s CD (group 8/57) (1-2 charges), range 0, AOE 5 circle, targets=Self + UnveiledGibbet = 24390, // L70, instant, 1.0s CD (group 0), range 3, single-target, targets=Hostile + Guillotine = 24384, // L70, instant, GCD, range 8, AOE 8+R ?-degree cone, targets=Hostile + Gibbet = 24382, // L70, instant, GCD, range 3, single-target, targets=Hostile + Gallows = 24383, // L70, instant, GCD, range 3, single-target, targets=Hostile + UnveiledGallows = 24391, // L70, instant, 1.0s CD (group 0), range 3, single-target, targets=Hostile + ArcaneCircle = 24405, // L72, instant, 120.0s CD (group 21), range 0, AOE 30 circle, targets=Self + Regress = 24403, // L74, instant, 1.0s CD (group 1), range 30, ???, targets=Area, animLock=0.800s? + Gluttony = 24393, // L76, instant, 60.0s CD (group 12), range 25, AOE 5 circle, targets=Hostile + GrimReaping = 24397, // L80, instant, GCD, range 8, AOE 8+R ?-degree cone, targets=Hostile + CrossReaping = 24396, // L80, instant, GCD, range 3, single-target, targets=Hostile + VoidReaping = 24395, // L80, instant, GCD, range 3, single-target, targets=Hostile + Enshroud = 24394, // L80, instant, 15.0s CD (group 3), range 0, single-target, targets=Self + SoulSow = 24387, // L82, 5.0s cast, GCD, range 0, single-target, targets=Self + HarvestMoon = 24388, // L82, instant, GCD, range 25, AOE 5 circle, targets=Hostile + LemuresSlice = 24399, // L86, instant, 1.0s CD (group 0), range 3, single-target, targets=Hostile + LemuresScythe = 24400, // L86, instant, 1.0s CD (group 0), range 8, AOE 8+R ?-degree cone, targets=Hostile + PlentifulHarvest = 24385, // L88, instant, GCD, range 15, AOE 15+R width 4 rect, targets=Hostile + Communio = 24398, // L90, 1.3s cast, GCD, range 25, AOE 5 circle, targets=Hostile + Sacrificium = 36969, // L92, instant, 1.0s CD (group 2), range 25, AOE 5 circle, targets=Hostile, animLock=??? + ExecutionersGibbet = 36970, // L96, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? + ExecutionersGallows = 36971, // L96, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? + ExecutionersGuillotine = 36972, // L96, instant, GCD, range 8, AOE 8+R ?-degree cone, targets=Hostile, animLock=??? + Perfectio = 36973, // L100, instant, GCD, range 25, AOE 5 circle, targets=Hostile, animLock=??? // Shared - Braver = ClassShared.AID.Braver, // LB1, 2.0s cast, range 8, single-target, targets=hostile, castAnimLock=3.860 - Bladedance = ClassShared.AID.Bladedance, // LB2, 3.0s cast, range 8, single-target, targets=hostile, castAnimLock=3.860 - SecondWind = ClassShared.AID.SecondWind, // L8, instant, 120.0s CD (group 49), range 0, single-target, targets=self - LegSweep = ClassShared.AID.LegSweep, // L10, instant, 40.0s CD (group 41), range 3, single-target, targets=hostile - Bloodbath = ClassShared.AID.Bloodbath, // L12, instant, 90.0s CD (group 46), range 0, single-target, targets=self - Feint = ClassShared.AID.Feint, // L22, instant, 90.0s CD (group 47), range 10, single-target, targets=hostile - ArmsLength = ClassShared.AID.ArmsLength, // L32, instant, 120.0s CD (group 48), range 0, single-target, targets=self - TrueNorth = ClassShared.AID.TrueNorth, // L50, instant, 45.0s CD (group 45/50) (2 charges), range 0, single-target, targets=self + Braver = ClassShared.AID.Braver, // LB1, 2.0s cast, range 8, single-target, targets=Hostile, animLock=3.860s? + Bladedance = ClassShared.AID.Bladedance, // LB2, 3.0s cast, range 8, single-target, targets=Hostile, animLock=3.860s? + SecondWind = ClassShared.AID.SecondWind, // L8, instant, 120.0s CD (group 49), range 0, single-target, targets=Self + LegSweep = ClassShared.AID.LegSweep, // L10, instant, 40.0s CD (group 43), range 3, single-target, targets=Hostile + Bloodbath = ClassShared.AID.Bloodbath, // L12, instant, 90.0s CD (group 46), range 0, single-target, targets=Self + Feint = ClassShared.AID.Feint, // L22, instant, 90.0s CD (group 47), range 10, single-target, targets=Hostile + ArmsLength = ClassShared.AID.ArmsLength, // L32, instant, 120.0s CD (group 48), range 0, single-target, targets=Self + TrueNorth = ClassShared.AID.TrueNorth, // L50, instant, 45.0s CD (group 45/50) (2 charges), range 0, single-target, targets=Self } public enum TraitID : uint @@ -64,6 +69,12 @@ public enum TraitID : uint DeathScytheMastery2 = 523, // L84 EnhancedShroud = 386, // L86 EnhancedArcaneCircle = 387, // L88 + EnhancedEnshroud = 594, // L92 + EnhancedSecondWind = 642, // L94 + MeleeMasteryIII = 657, // L94 + EnhancedGluttony = 596, // L96 + EnhancedFeint = 641, // L98 + EnhancedPlentifulHarvest = 597, // L100 } // TODO: regenerate @@ -88,6 +99,11 @@ public enum SID : uint Bloodbath = 84, Feint = 1195, Stun = 2, + IdealHost = 3905, + Oblatio = 3857, + Executioner = 3858, + PerfectioOcculta = 3859, + PerfectioParata = 3860 } public sealed class Definitions : IDisposable @@ -96,13 +112,13 @@ public sealed class Definitions : IDisposable public Definitions(ActionDefinitions d) { - d.RegisterSpell(AID.TheEnd, castAnimLock: 3.70f); // animLock=???, castAnimLock=3.700 + d.RegisterSpell(AID.TheEnd, castAnimLock: 3.70f); // animLock=3.700s? d.RegisterSpell(AID.Slice); d.RegisterSpell(AID.WaxingSlice); d.RegisterSpell(AID.ShadowofDeath); d.RegisterSpell(AID.Harpe); - d.RegisterSpell(AID.HellsIngress, instantAnimLock: 0.80f); // animLock=0.800 - d.RegisterSpell(AID.HellsEgress, instantAnimLock: 0.80f); // animLock=0.800 + d.RegisterSpell(AID.HellsEgress, instantAnimLock: 0.80f); // animLock=0.800s? + d.RegisterSpell(AID.HellsIngress, instantAnimLock: 0.80f); // animLock=0.800s? d.RegisterSpell(AID.SpinningScythe); d.RegisterSpell(AID.InfernalSlice); d.RegisterSpell(AID.WhorlofDeath); @@ -113,23 +129,28 @@ public Definitions(ActionDefinitions d) d.RegisterSpell(AID.SoulSlice); d.RegisterSpell(AID.SoulScythe); d.RegisterSpell(AID.UnveiledGibbet); + d.RegisterSpell(AID.Guillotine); d.RegisterSpell(AID.Gibbet); - d.RegisterSpell(AID.UnveiledGallows); d.RegisterSpell(AID.Gallows); - d.RegisterSpell(AID.Guillotine); + d.RegisterSpell(AID.UnveiledGallows); d.RegisterSpell(AID.ArcaneCircle); - d.RegisterSpell(AID.Regress, instantAnimLock: 0.80f); // animLock=0.800 + d.RegisterSpell(AID.Regress, instantAnimLock: 0.80f); // animLock=0.800s? d.RegisterSpell(AID.Gluttony); - d.RegisterSpell(AID.Enshroud); - d.RegisterSpell(AID.VoidReaping); - d.RegisterSpell(AID.CrossReaping); d.RegisterSpell(AID.GrimReaping); - d.RegisterSpell(AID.HarvestMoon); + d.RegisterSpell(AID.CrossReaping); + d.RegisterSpell(AID.VoidReaping); + d.RegisterSpell(AID.Enshroud); d.RegisterSpell(AID.SoulSow); + d.RegisterSpell(AID.HarvestMoon); d.RegisterSpell(AID.LemuresSlice); d.RegisterSpell(AID.LemuresScythe); d.RegisterSpell(AID.PlentifulHarvest); - d.RegisterSpell(AID.Communio); // animLock=??? + d.RegisterSpell(AID.Communio); + d.RegisterSpell(AID.Sacrificium); // animLock=??? + d.RegisterSpell(AID.ExecutionersGibbet); // animLock=??? + d.RegisterSpell(AID.ExecutionersGallows); // animLock=??? + d.RegisterSpell(AID.ExecutionersGuillotine); // animLock=??? + d.RegisterSpell(AID.Perfectio); // animLock=??? Customize(d); } diff --git a/BossMod/ActionQueue/Melee/VPR.cs b/BossMod/ActionQueue/Melee/VPR.cs index 5782081e9f..0a7ce74bdd 100644 --- a/BossMod/ActionQueue/Melee/VPR.cs +++ b/BossMod/ActionQueue/Melee/VPR.cs @@ -5,55 +5,55 @@ public enum AID : uint None = 0, Sprint = ClassShared.AID.Sprint, - WorldSwallower = 34866, // LB3, 4.5s cast, range 8, single-target, targets=Hostile, animLock=??? - SteelFangs = 34606, // L1, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - HuntersSting = 34608, // L5, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - DreadFangs = 34607, // L10, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - WrithingSnap = 34632, // L15, instant, GCD, range 20, single-target, targets=Hostile, animLock=??? - SwiftskinsSting = 34609, // L20, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - SteelMaw = 34614, // L25, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - FlankstingStrike = 34610, // L30, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - FlanksbaneFang = 34611, // L30, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - HindstingStrike = 34612, // L30, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - HindsbaneFang = 34613, // L30, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - DreadMaw = 34615, // L35, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - Slither = 34646, // L40, instant, 30.0s CD (group 13/70) (2? charges), range 20, single-target, targets=Party/Hostile, animLock=??? - HuntersBite = 34616, // L40, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - SwiftskinsBite = 34617, // L45, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - JaggedMaw = 34618, // L50, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - BloodiedMaw = 34619, // L50, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - DeathRattle = 34634, // L55, instant, 1.0s CD (group 0), range 3, single-target, targets=Hostile, animLock=??? - SerpentsTail = 35920, // L55, instant, 1.0s CD (group 0), range 0, single-target, targets=Self, animLock=??? - LastLash = 34635, // L60, instant, 1.0s CD (group 0), range 0, AOE 5 circle, targets=Self, animLock=??? - Dreadwinder = 34620, // L65, instant, 40.0s CD (group 14/57) (2? charges), range 3, single-target, targets=Hostile, animLock=??? - HuntersCoil = 34621, // L65, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - SwiftskinsCoil = 34622, // L65, instant, GCD, range 3, single-target, targets=Hostile, animLock=??? - PitOfDread = 34623, // L70, instant, 40.0s CD (group 14/57) (2? charges), range 0, AOE 5 circle, targets=Self, animLock=??? - SwiftskinsDen = 34625, // L70, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - HuntersDen = 34624, // L70, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - TwinfangBite = 34636, // L75, instant, 1.0s CD (group 1), range 3, single-target, targets=Hostile, animLock=??? - TwinbloodBite = 34637, // L75, instant, 1.0s CD (group 2), range 3, single-target, targets=Hostile, animLock=??? - Twinblood = 35922, // L75, instant, 1.0s CD (group 2), range 0, single-target, targets=Self, animLock=??? - Twinfang = 35921, // L75, instant, 1.0s CD (group 1), range 0, single-target, targets=Self, animLock=??? - TwinfangThresh = 34638, // L80, instant, 1.0s CD (group 1), range 0, AOE 5 circle, targets=Self, animLock=??? - TwinbloodThresh = 34639, // L80, instant, 1.0s CD (group 2), range 0, AOE 5 circle, targets=Self, animLock=??? - UncoiledFury = 34633, // L82, instant, GCD, range 20, AOE 5 circle, targets=Hostile, animLock=??? - SerpentsIre = 34647, // L86, instant, 120.0s CD (group 19), range 0, single-target, targets=Self, animLock=??? - ThirdGeneration = 34629, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile, animLock=??? - FourthGeneration = 34630, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile, animLock=??? - SecondGeneration = 34628, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile, animLock=??? - FirstGeneration = 34627, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile, animLock=??? - Reawaken = 34626, // L90, instant, GCD, range 0, AOE 5 circle, targets=Self, animLock=??? - UncoiledTwinfang = 34644, // L92, instant, 1.0s CD (group 1), range 20, AOE 5 circle, targets=Hostile, animLock=??? - UncoiledTwinblood = 34645, // L92, instant, 1.0s CD (group 2), range 20, AOE 5 circle, targets=Hostile, animLock=??? - Ouroboros = 34631, // L96, instant, GCD, range 3, AOE 5 circle, targets=Hostile, animLock=??? - FourthLegacy = 34643, // L100, instant, 1.0s CD (group 0), range 3, AOE 5 circle, targets=Hostile, animLock=??? - SecondLegacy = 34641, // L100, instant, 1.0s CD (group 0), range 3, AOE 5 circle, targets=Hostile, animLock=??? - FirstLegacy = 34640, // L100, instant, 1.0s CD (group 0), range 3, AOE 5 circle, targets=Hostile, animLock=??? - ThirdLegacy = 34642, // L100, instant, 1.0s CD (group 0), range 3, AOE 5 circle, targets=Hostile, animLock=??? + WorldSwallower = 34866, // LB3, 4.5s cast, range 8, single-target, targets=Hostile + SteelFangs = 34606, // L1, instant, GCD, range 3, single-target, targets=Hostile + HuntersSting = 34608, // L5, instant, GCD, range 3, single-target, targets=Hostile + ReavingFangs = 34607, // L10, instant, GCD, range 3, single-target, targets=Hostile + WrithingSnap = 34632, // L15, instant, GCD, range 20, single-target, targets=Hostile + SwiftskinsSting = 34609, // L20, instant, GCD, range 3, single-target, targets=Hostile + SteelMaw = 34614, // L25, instant, GCD, range 0, AOE 5 circle, targets=Self + FlankstingStrike = 34610, // L30, instant, GCD, range 3, single-target, targets=Hostile + FlanksbaneFang = 34611, // L30, instant, GCD, range 3, single-target, targets=Hostile + HindstingStrike = 34612, // L30, instant, GCD, range 3, single-target, targets=Hostile + HindsbaneFang = 34613, // L30, instant, GCD, range 3, single-target, targets=Hostile + ReavingMaw = 34615, // L35, instant, GCD, range 0, AOE 5 circle, targets=Self + Slither = 34646, // L40, instant, 30.0s CD (group 13/70) (2-3 charges), range 20, single-target, targets=Party/Hostile + HuntersBite = 34616, // L40, instant, GCD, range 0, AOE 5 circle, targets=Self + SwiftskinsBite = 34617, // L45, instant, GCD, range 0, AOE 5 circle, targets=Self + JaggedMaw = 34618, // L50, instant, GCD, range 0, AOE 5 circle, targets=Self + BloodiedMaw = 34619, // L50, instant, GCD, range 0, AOE 5 circle, targets=Self + DeathRattle = 34634, // L55, instant, 1.0s CD (group 0), range 5, single-target, targets=Hostile + SerpentsTail = 35920, // L55, instant, 1.0s CD (group 0), range 0, single-target, targets=Self + LastLash = 34635, // L60, instant, 1.0s CD (group 0), range 0, AOE 5 circle, targets=Self + Vicewinder = 34620, // L65, instant, 40.0s CD (group 14/57) (2 charges), range 3, single-target, targets=Hostile + HuntersCoil = 34621, // L65, instant, GCD, range 3, single-target, targets=Hostile + SwiftskinsCoil = 34622, // L65, instant, GCD, range 3, single-target, targets=Hostile + Vicepit = 34623, // L70, instant, 40.0s CD (group 14/57) (2 charges), range 0, AOE 5 circle, targets=Self + SwiftskinsDen = 34625, // L70, instant, GCD, range 0, AOE 5 circle, targets=Self + HuntersDen = 34624, // L70, instant, GCD, range 0, AOE 5 circle, targets=Self + TwinfangBite = 34636, // L75, instant, 1.0s CD (group 1), range 5, single-target, targets=Hostile + TwinbloodBite = 34637, // L75, instant, 1.0s CD (group 2), range 5, single-target, targets=Hostile + Twinblood = 35922, // L75, instant, 1.0s CD (group 2), range 0, single-target, targets=Self + Twinfang = 35921, // L75, instant, 1.0s CD (group 1), range 0, single-target, targets=Self + TwinfangThresh = 34638, // L80, instant, 1.0s CD (group 1), range 0, AOE 5 circle, targets=Self + TwinbloodThresh = 34639, // L80, instant, 1.0s CD (group 2), range 0, AOE 5 circle, targets=Self + UncoiledFury = 34633, // L82, instant, GCD, range 20, AOE 5 circle, targets=Hostile + SerpentsIre = 34647, // L86, instant, 120.0s CD (group 19), range 0, single-target, targets=Self + ThirdGeneration = 34629, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile + FourthGeneration = 34630, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile + SecondGeneration = 34628, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile + FirstGeneration = 34627, // L90, instant, GCD, range 3, AOE 5 circle, targets=Hostile + Reawaken = 34626, // L90, instant, GCD, range 0, AOE 5 circle, targets=Self + UncoiledTwinfang = 34644, // L92, instant, 1.0s CD (group 1), range 20, AOE 5 circle, targets=Hostile + UncoiledTwinblood = 34645, // L92, instant, 1.0s CD (group 2), range 20, AOE 5 circle, targets=Hostile + Ouroboros = 34631, // L96, instant, GCD, range 3, AOE 5 circle, targets=Hostile + FourthLegacy = 34643, // L100, instant, 1.0s CD (group 0), range 5, AOE 5 circle, targets=Hostile + SecondLegacy = 34641, // L100, instant, 1.0s CD (group 0), range 5, AOE 5 circle, targets=Hostile + FirstLegacy = 34640, // L100, instant, 1.0s CD (group 0), range 5, AOE 5 circle, targets=Hostile + ThirdLegacy = 34642, // L100, instant, 1.0s CD (group 0), range 5, AOE 5 circle, targets=Hostile // Shared - Braver = ClassShared.AID.Braver, // LB1, 2.0s cast, range 8, single-target, targets=Hostile, animLock=3.860s? + Braver = ClassShared.AID.Braver, // LB1, 2.0s cast, range 8, single-target, targets=Hostile, castAnimLock=3.860 Bladedance = ClassShared.AID.Bladedance, // LB2, 3.0s cast, range 8, single-target, targets=Hostile, animLock=3.860s? SecondWind = ClassShared.AID.SecondWind, // L8, instant, 120.0s CD (group 49), range 0, single-target, targets=Self LegSweep = ClassShared.AID.LegSweep, // L10, instant, 40.0s CD (group 43), range 3, single-target, targets=Hostile @@ -82,12 +82,14 @@ public enum TraitID : uint EnhancedFeint = 641, // L98 SerpentsLegacy = 534, // L100 } + public enum SID : uint { None = 0, HuntersInstinct = 3668, // applied by Hunter's Sting, Hunter's Bite, Hunter's Coil, Hunter's Den to self - NoxiousGnash = 3667, // applied by Dread Fangs to target Swiftscaled = 3669, // applied by Swiftskin's Sting, Swiftskin's Bite, Swiftskin's Coil, Swiftskin's Den to self + HonedSteel = 3672, + HonedReavers = 3772, HindstungVenom = 3647, // applied by Flanksting Strike to self HindsbaneVenom = 3648, // applied by Flanksbane Fang to self FlanksbaneVenom = 3646, // applied by Hindsting Strike to self @@ -110,52 +112,52 @@ public sealed class Definitions : IDisposable { public Definitions(ActionDefinitions d) { - d.RegisterSpell(AID.WorldSwallower); // animLock=??? - d.RegisterSpell(AID.SteelFangs); // animLock=??? - d.RegisterSpell(AID.HuntersSting); // animLock=??? - d.RegisterSpell(AID.DreadFangs); // animLock=??? - d.RegisterSpell(AID.WrithingSnap); // animLock=??? - d.RegisterSpell(AID.SwiftskinsSting); // animLock=??? - d.RegisterSpell(AID.SteelMaw); // animLock=??? - d.RegisterSpell(AID.FlankstingStrike); // animLock=??? - d.RegisterSpell(AID.FlanksbaneFang); // animLock=??? - d.RegisterSpell(AID.HindstingStrike); // animLock=??? - d.RegisterSpell(AID.HindsbaneFang); // animLock=??? - d.RegisterSpell(AID.DreadMaw); // animLock=??? - d.RegisterSpell(AID.Slither); // animLock=??? - d.RegisterSpell(AID.HuntersBite); // animLock=??? - d.RegisterSpell(AID.SwiftskinsBite); // animLock=??? - d.RegisterSpell(AID.JaggedMaw); // animLock=??? - d.RegisterSpell(AID.BloodiedMaw); // animLock=??? - d.RegisterSpell(AID.DeathRattle); // animLock=??? - d.RegisterSpell(AID.SerpentsTail); // animLock=??? - d.RegisterSpell(AID.LastLash); // animLock=??? - d.RegisterSpell(AID.Dreadwinder); // animLock=??? - d.RegisterSpell(AID.HuntersCoil); // animLock=??? - d.RegisterSpell(AID.SwiftskinsCoil); // animLock=??? - d.RegisterSpell(AID.PitOfDread); // animLock=??? - d.RegisterSpell(AID.SwiftskinsDen); // animLock=??? - d.RegisterSpell(AID.HuntersDen); // animLock=??? - d.RegisterSpell(AID.TwinfangBite); // animLock=??? - d.RegisterSpell(AID.TwinbloodBite); // animLock=??? - d.RegisterSpell(AID.Twinblood); // animLock=??? - d.RegisterSpell(AID.Twinfang); // animLock=??? - d.RegisterSpell(AID.TwinfangThresh); // animLock=??? - d.RegisterSpell(AID.TwinbloodThresh); // animLock=??? - d.RegisterSpell(AID.UncoiledFury); // animLock=??? - d.RegisterSpell(AID.SerpentsIre); // animLock=??? - d.RegisterSpell(AID.FirstGeneration); // animLock=??? - d.RegisterSpell(AID.SecondGeneration); // animLock=??? - d.RegisterSpell(AID.ThirdGeneration); // animLock=??? - d.RegisterSpell(AID.FourthGeneration); // animLock=??? - d.RegisterSpell(AID.Reawaken); // animLock=??? - d.RegisterSpell(AID.UncoiledTwinfang); // animLock=??? - d.RegisterSpell(AID.UncoiledTwinblood); // animLock=??? - d.RegisterSpell(AID.Ouroboros); // animLock=??? - d.RegisterSpell(AID.FourthLegacy); // animLock=??? - d.RegisterSpell(AID.SecondLegacy); // animLock=??? - d.RegisterSpell(AID.FirstLegacy); // animLock=??? - d.RegisterSpell(AID.ThirdLegacy); // animLock=??? + d.RegisterSpell(AID.WorldSwallower); + d.RegisterSpell(AID.SteelFangs); + d.RegisterSpell(AID.HuntersSting); + d.RegisterSpell(AID.ReavingFangs); + d.RegisterSpell(AID.WrithingSnap); + d.RegisterSpell(AID.SwiftskinsSting); + d.RegisterSpell(AID.SteelMaw); + d.RegisterSpell(AID.FlankstingStrike); + d.RegisterSpell(AID.FlanksbaneFang); + d.RegisterSpell(AID.HindstingStrike); + d.RegisterSpell(AID.HindsbaneFang); + d.RegisterSpell(AID.ReavingMaw); + d.RegisterSpell(AID.Slither); + d.RegisterSpell(AID.HuntersBite); + d.RegisterSpell(AID.SwiftskinsBite); + d.RegisterSpell(AID.JaggedMaw); + d.RegisterSpell(AID.BloodiedMaw); + d.RegisterSpell(AID.DeathRattle); + d.RegisterSpell(AID.SerpentsTail); + d.RegisterSpell(AID.LastLash); + d.RegisterSpell(AID.Vicewinder); + d.RegisterSpell(AID.HuntersCoil); + d.RegisterSpell(AID.SwiftskinsCoil); + d.RegisterSpell(AID.Vicepit); + d.RegisterSpell(AID.SwiftskinsDen); + d.RegisterSpell(AID.HuntersDen); + d.RegisterSpell(AID.TwinfangBite); + d.RegisterSpell(AID.TwinbloodBite); + d.RegisterSpell(AID.Twinblood); + d.RegisterSpell(AID.Twinfang); + d.RegisterSpell(AID.TwinfangThresh); + d.RegisterSpell(AID.TwinbloodThresh); + d.RegisterSpell(AID.UncoiledFury); + d.RegisterSpell(AID.SerpentsIre); + d.RegisterSpell(AID.ThirdGeneration); + d.RegisterSpell(AID.FourthGeneration); + d.RegisterSpell(AID.SecondGeneration); + d.RegisterSpell(AID.FirstGeneration); + d.RegisterSpell(AID.Reawaken); + d.RegisterSpell(AID.UncoiledTwinfang); + d.RegisterSpell(AID.UncoiledTwinblood); + d.RegisterSpell(AID.Ouroboros); + d.RegisterSpell(AID.FourthLegacy); + d.RegisterSpell(AID.SecondLegacy); + d.RegisterSpell(AID.FirstLegacy); + d.RegisterSpell(AID.ThirdLegacy); Customize(d); } diff --git a/BossMod/Autorotation/xan/Basexan.cs b/BossMod/Autorotation/xan/Basexan.cs index 0cbf7d307b..d369a8c41b 100644 --- a/BossMod/Autorotation/xan/Basexan.cs +++ b/BossMod/Autorotation/xan/Basexan.cs @@ -7,19 +7,19 @@ public enum AOEStrategy { ST, AOE, ForceAOE, ForceST } public enum SharedTrack { Targeting, AOE, Buffs, Count } public abstract class Attackxan(RotationModuleManager manager, Actor player) : Basexan(manager, player) - where AID : Enum where TraitID : Enum + where AID : struct, Enum where TraitID : Enum { protected sealed override float GCDLength => AttackGCDLength; } public abstract class Castxan(RotationModuleManager manager, Actor player) : Basexan(manager, player) - where AID : Enum where TraitID : Enum + where AID : struct, Enum where TraitID : Enum { protected sealed override float GCDLength => SpellGCDLength; } public abstract class Basexan(RotationModuleManager manager, Actor player) : RotationModule(manager, player) - where AID : Enum where TraitID : Enum + where AID : struct, Enum where TraitID : Enum { protected float PelotonLeft { get; private set; } protected float SwiftcastLeft { get; private set; } @@ -28,6 +28,9 @@ public abstract class Basexan(RotationModuleManager manager, Actor protected float AnimationLockDelay { get; private set; } protected float RaidBuffsIn { get; private set; } protected float RaidBuffsLeft { get; private set; } + protected float DowntimeIn { get; private set; } + + protected float? CountdownRemaining => World.Client.CountdownRemaining; protected float AttackGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); protected float SpellGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SpellSpeed, World.Client.PlayerStats.Haste, Player.Level); @@ -41,40 +44,58 @@ public abstract class Basexan(RotationModuleManager manager, Actor protected float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; - public bool CanWeave(float cooldown, float actionLock, int extraGCDs = 0) - => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + GCDLength * extraGCDs; - public bool CanWeave(AID aid, int extraGCDs = 0) + public bool CanWeave(float cooldown, float actionLock, int extraGCDs = 0, float extraFixedDelay = 0) + => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + GCDLength * extraGCDs + extraFixedDelay; + public bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) { + // TODO is this actually helpful? + if (!Unlocked(aid)) + return false; + var def = ActionDefinitions.Instance[ActionID.MakeSpell(aid)]!; - return CanWeave(CD(aid), def.InstantAnimLock, extraGCDs); + return CanWeave(CD(aid), def.InstantAnimLock, extraGCDs, extraFixedDelay); } + protected AID NextGCD; + protected int NextGCDPrio; protected uint MP; protected AID ComboLastMove => (AID)(object)World.Client.ComboState.Action; - protected void PushGCD(AID aid, Actor? target, int additionalPrio = 0, float delay = 0) - => PushAction(aid, target, ActionQueue.Priority.High + 500 + additionalPrio, delay); + protected void PushGCD

(AID aid, Actor? target, P priority, float delay = 0) where P : Enum + => PushGCD(aid, target, (int)(object)priority, delay); + + protected void PushGCD(AID aid, Actor? target, int priority = 0, float delay = 0) + { + if (priority < 0) + return; + + if (PushAction(aid, target, ActionQueue.Priority.High + 100 + priority, delay) && priority > NextGCDPrio) + { + NextGCD = aid; + NextGCDPrio = priority; + } + } - protected void PushOGCD(AID aid, Actor? target, int additionalPrio = 0, float delay = 0) - => PushAction(aid, target, ActionQueue.Priority.Low + 500 + additionalPrio, delay); + protected void PushOGCD(AID aid, Actor? target, int priority = 0, float delay = 0) + => PushAction(aid, target, ActionQueue.Priority.Low + 100 + priority, delay); - protected void PushAction(AID aid, Actor? target, float priority, float delay) + protected bool PushAction(AID aid, Actor? target, float priority, float delay) { if ((uint)(object)aid == 0) - return; + return false; if (!CanCast(aid)) - return; + return false; var def = ActionDefinitions.Instance.Spell(aid); if (def == null) - return; + return false; if (def.Range != 0 && target == null) { // Service.Log($"Queued targeted action ({aid}) with no target"); - return; + return false; } Vector3 targetPos = default; @@ -82,6 +103,7 @@ protected void PushAction(AID aid, Actor? target, float priority, float delay) targetPos = Player.PosRot.XYZ(); Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, priority, targetPos: targetPos, delay: delay); + return true; } ///

@@ -211,7 +233,7 @@ protected int AdjustNumTargets(StrategyValues strategy, int reported) /// /// /// - protected virtual float GetCastTime(AID aid) => SwiftcastLeft > GCD ? 0 : ActionDefinitions.Instance.Spell(aid)!.CastTime * SpellGCDLength / 2.5f; + protected virtual float GetCastTime(AID aid) => SwiftcastLeft > GCD ? 0 : ActionDefinitions.Instance.Spell(aid)!.CastTime * GCDLength / 2.5f; protected float NextCastStart => World.Client.AnimationLock > GCD ? World.Client.AnimationLock + AnimationLockDelay : GCD; @@ -262,7 +284,7 @@ public sealed override void Execute(StrategyValues strategy, Actor? primaryTarge { var pelo = Player.FindStatus(BRD.SID.Peloton); PelotonLeft = pelo != null ? StatusDuration(pelo.Value.ExpireAt) : 0; - SwiftcastLeft = StatusLeft(WHM.SID.Swiftcast); + SwiftcastLeft = StatusLeft(BossMod.WHM.SID.Swiftcast); TrueNorthLeft = StatusLeft(DRG.SID.TrueNorth); ForceMovementIn = forceMovementIn; @@ -270,6 +292,7 @@ public sealed override void Execute(StrategyValues strategy, Actor? primaryTarge CombatTimer = (float)(World.CurrentTime - Manager.CombatStart).TotalSeconds; (RaidBuffsLeft, RaidBuffsIn) = EstimateRaidBuffTimings(primaryTarget); + DowntimeIn = Manager.Planner?.EstimateTimeToNextDowntime().Item2 ?? float.MaxValue; // TODO max MP can be higher in eureka/bozja MP = (uint)Math.Clamp(Player.HPMP.CurMP + World.PendingEffects.PendingMPDifference(Player.InstanceID), 0, 10000); @@ -277,12 +300,25 @@ public sealed override void Execute(StrategyValues strategy, Actor? primaryTarge Exec(strategy, primaryTarget); } + private new (float Left, float In) EstimateRaidBuffTimings(Actor? primaryTarget) + { + if (primaryTarget?.OID != 0x385) + return (Bossmods.RaidCooldowns.DamageBuffLeft(Player), Bossmods.RaidCooldowns.NextDamageBuffIn2()); + + return base.EstimateRaidBuffTimings(primaryTarget); + } + public abstract void Exec(StrategyValues strategy, Actor? primaryTarget); protected (float Left, int Stacks) Status(SID status) where SID : Enum => Player.FindStatus(status) is ActorStatus s ? (StatusDuration(s.ExpireAt), s.Extra & 0xFF) : (0, 0); protected float StatusLeft(SID status) where SID : Enum => Status(status).Left; protected int StatusStacks(SID status) where SID : Enum => Status(status).Stacks; + protected float HPRatio(Actor actor) => (float)actor.HPMP.CurHP / Player.HPMP.MaxHP; + protected float HPRatio() => HPRatio(Player); + + protected uint PredictedHP(Actor actor) => (uint)Math.Clamp(actor.HPMP.CurHP + World.PendingEffects.PendingHPDifference(actor.InstanceID), 0, actor.HPMP.MaxHP); + protected float PredictedHPRatio(Actor actor) => (float)PredictedHP(actor) / actor.HPMP.MaxHP; } static class Extendxan diff --git a/BossMod/Autorotation/xan/Melee/DRG.cs b/BossMod/Autorotation/xan/Melee/DRG.cs index a445082574..1fba59f1de 100644 --- a/BossMod/Autorotation/xan/Melee/DRG.cs +++ b/BossMod/Autorotation/xan/Melee/DRG.cs @@ -15,7 +15,7 @@ public enum DiveStrategy public static RotationModuleDefinition Definition() { - var def = new RotationModuleDefinition("xan DRG", "Dragoon", "xan", RotationModuleQuality.WIP, BitMask.Build(Class.DRG, Class.LNC), 100); + var def = new RotationModuleDefinition("xan DRG", "Dragoon", "xan", RotationModuleQuality.Basic, BitMask.Build(Class.DRG, Class.LNC), 100); def.DefineShared().AddAssociatedActions(AID.BattleLitany, AID.LanceCharge); @@ -37,6 +37,7 @@ public static RotationModuleDefinition Definition() public float LifeSurge; public float DraconianFire; public float DragonsFlight; + public float StarcrossReady; public float TargetDotLeft; @@ -65,6 +66,7 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) LanceCharge = StatusLeft(SID.LanceCharge); DraconianFire = StatusLeft(SID.DraconianFire); DragonsFlight = StatusLeft(SID.DragonsFlight); + StarcrossReady = StatusLeft(SID.StarcrossReady); TargetDotLeft = MathF.Max( StatusDetails(primaryTarget, SID.ChaosThrust, Player.InstanceID).Left, StatusDetails(primaryTarget, SID.ChaoticSpring, Player.InstanceID).Left @@ -150,7 +152,7 @@ private void OGCD(StrategyValues strategy, Actor? primaryTarget) var posOk = PosLockOk(strategy); if (NextPositionalImminent && !NextPositionalCorrect) - PushOGCD(AID.TrueNorth, Player, additionalPrio: -20, delay: GCD - 0.8f); + PushOGCD(AID.TrueNorth, Player, priority: -20, delay: GCD - 0.8f); if (strategy.BuffsOk()) { @@ -164,6 +166,14 @@ private void OGCD(StrategyValues strategy, Actor? primaryTarget) if (CD(AID.LanceCharge) == 0) return; + if (LanceCharge > GCD && ShouldLifeSurge()) + PushOGCD(AID.LifeSurge, Player); + + if (StarcrossReady > 0) + // TODO we *technically* should select a specific target for starcross because it's a 3y range 5y radius circle... + // but it's always gonna get used immediately after stardiver and we'll be melee range...so fuck it + PushOGCD(AID.Starcross, primaryTarget); + if (LotD > 0 && moveOk) PushOGCD(AID.Stardiver, BestDiveTarget); @@ -173,9 +183,6 @@ private void OGCD(StrategyValues strategy, Actor? primaryTarget) if (DiveReady == 0 && posOk) PushOGCD(AID.Jump, primaryTarget); - if (LanceCharge > GCD && ShouldLifeSurge()) - PushOGCD(AID.LifeSurge, Player); - if (moveOk) PushOGCD(AID.DragonfireDive, BestDiveTarget); diff --git a/BossMod/Autorotation/xan/Melee/MNK.cs b/BossMod/Autorotation/xan/Melee/MNK.cs index 8d7d8678b0..7c603ba326 100644 --- a/BossMod/Autorotation/xan/Melee/MNK.cs +++ b/BossMod/Autorotation/xan/Melee/MNK.cs @@ -70,7 +70,12 @@ public enum Form { None, OpoOpo, Raptor, Coeurl } public bool CanFormShift => Unlocked(AID.FormShift) && PerfectBalanceLeft == 0; - private (Positional, bool) NextPositional => (CoeurlStacks > 0 ? Positional.Flank : Positional.Rear, EffectiveForm == Form.Coeurl); + public const int AOEBreakpoint = 4; + public bool UseAOE => NumAOETargets >= AOEBreakpoint; + + private (Positional, bool) NextPositional => UseAOE + ? (Positional.Any, false) + : (CoeurlStacks > 0 ? Positional.Flank : Positional.Rear, EffectiveForm == Form.Coeurl); public override void Exec(StrategyValues strategy, Actor? primaryTarget) { @@ -123,15 +128,16 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) OGCD(strategy, primaryTarget); + // minimize priority of these actions if (Chakra < 5 && Unlocked(AID.SteeledMeditation)) - PushGCD(AID.SteeledMeditation, Player, -4500); - - if (Unlocked(AID.FormShift) && PerfectBalanceLeft == 0 && FormShiftLeft < 5) - PushGCD(AID.FormShift, Player, -4500); + PushAction(AID.SteeledMeditation, Player, ActionQueue.Priority.Minimal + 1, 0); - if (World.Client.CountdownRemaining > 0) + if (CountdownRemaining > 0) { - if (World.Client.CountdownRemaining < 0.2 && Player.DistanceToHitbox(primaryTarget) is > 3 and < 25) + if (CountdownRemaining is > 2 and < 8 && FormShiftLeft == 0) + PushGCD(AID.FormShift, Player); + + if (CountdownRemaining < 0.4 && Player.DistanceToHitbox(primaryTarget) is > 3 and < 25) PushGCD(AID.Thunderclap, primaryTarget); return; @@ -153,12 +159,11 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) PushGCD(AID.WindsReply, BestLineTarget); } - if (NumAOETargets > 2 && Unlocked(AID.ArmOfTheDestroyer)) + if (UseAOE && Unlocked(AID.ArmOfTheDestroyer)) { if (EffectiveForm == Form.Coeurl) PushGCD(AID.Rockbreaker, Player); - // TODO this is actually still suboptimal on 3 targets if (EffectiveForm == Form.Raptor) PushGCD(AID.FourPointFury, Player); @@ -185,8 +190,10 @@ private Form EffectiveForm if (PerfectBalanceLeft == 0) return CurrentForm; - // hack: allow double lunar opener - var forcedSolar = ForcedSolar || HasLunar && !HasSolar && CombatTimer > 30; + // hack: allow double lunar opener - only in boss fights + // trash packs should get regular lunar solar + var forceDoubleLunar = CombatTimer < 30 && !UseAOE; + var forcedSolar = ForcedSolar || HasLunar && !HasSolar && !forceDoubleLunar; var canCoeurl = forcedSolar; var canRaptor = forcedSolar; @@ -204,33 +211,25 @@ private Form EffectiveForm } } + // TODO the monk document is updated AGAIN, now we early PB in solar windows again????????????? + // FUCK private void QueuePB(StrategyValues strategy) { if (CurrentForm != Form.Raptor || BeastChakra[0] != BeastChakraType.None || FiresReplyLeft > GCD) return; // prevent odd window double blitz - if (HasBothNadi && FireLeft > 0) + // TODO figure out the actual mathematical equation that differentiates odd windows, this is stupid + if (BrotherhoodLeft == 0 && CD(AID.PerfectBalance) > 30) return; - // TODO forced solar in strategy - // default: solar in odd windows only, opener/2m is always lunar - var wantSolar = HasLunar && !HasSolar && FireLeft == 0; - - // earliest we can press PB before next RoF - var gcdsAhead = wantSolar ? 1 : 2; - - if (CanWeave(AID.RiddleOfFire, gcdsAhead)) - PushOGCD(AID.PerfectBalance, Player); - - // can PB if we have 4 GCDs worth of buff remaining - if (CanFitGCD(FireLeft, 3)) - PushOGCD(AID.PerfectBalance, Player); + if (CanWeave(AID.RiddleOfFire, 3) || CanFitGCD(FireLeft, 3)) + PushAction(AID.PerfectBalance, Player, ActionQueue.Priority.ManualOGCD + 1, 0); } private void OGCD(StrategyValues strategy, Actor? primaryTarget) { - if (!Player.InCombat || GCD == 0) + if (!Player.InCombat || GCD == 0 || primaryTarget == null) return; if (strategy.BuffsOk()) @@ -238,10 +237,10 @@ private void OGCD(StrategyValues strategy, Actor? primaryTarget) QueuePB(strategy); if (CombatTimer >= 10 || BeastCount == 3) - PushOGCD(AID.Brotherhood, Player); + PushAction(AID.Brotherhood, Player, ActionQueue.Priority.ManualOGCD + 3, 0); if (ShouldRoF) - PushOGCD(AID.RiddleOfFire, Player, delay: GCD - 0.8f); + PushAction(AID.RiddleOfFire, Player, ActionQueue.Priority.ManualOGCD + 2, GCD - 0.8f); if (CD(AID.RiddleOfFire) > 0) PushOGCD(AID.RiddleOfWind, Player); diff --git a/BossMod/Autorotation/xan/Melee/NIN.cs b/BossMod/Autorotation/xan/Melee/NIN.cs index 8428294361..a839225fb0 100644 --- a/BossMod/Autorotation/xan/Melee/NIN.cs +++ b/BossMod/Autorotation/xan/Melee/NIN.cs @@ -104,10 +104,10 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) OGCD(strategy, primaryTarget); - if (World.Client.CountdownRemaining > 0) + if (CountdownRemaining > 0) { - if (World.Client.CountdownRemaining < 6) - UseMudra(AID.Suiton, primaryTarget, endCondition: World.Client.CountdownRemaining < 1); + if (CountdownRemaining < 6) + UseMudra(AID.Suiton, primaryTarget, endCondition: CountdownRemaining < 1); return; } @@ -355,7 +355,7 @@ private void OGCD(StrategyValues strategy, Actor? primaryTarget) if (NumRangedAOETargets > 2 || !Unlocked(AID.Bhavacakra)) PushOGCD(AID.HellfrogMedium, BestRangedAOETarget); - PushOGCD(AID.Bhavacakra, primaryTarget, Meisui > 0 ? 50 : 0); + PushOGCD(AID.Bhavacakra, primaryTarget, priority: Meisui > 0 ? 50 : 0); } } diff --git a/BossMod/Autorotation/xan/Melee/RPR.cs b/BossMod/Autorotation/xan/Melee/RPR.cs new file mode 100644 index 0000000000..69290015c6 --- /dev/null +++ b/BossMod/Autorotation/xan/Melee/RPR.cs @@ -0,0 +1,374 @@ +using BossMod.RPR; +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; + +namespace BossMod.Autorotation.xan; + +public sealed class RPR(RotationModuleManager manager, Actor player) : Attackxan(manager, player) +{ + public static RotationModuleDefinition Definition() + { + var def = new RotationModuleDefinition("xan RPR", "Reaper", "xan", RotationModuleQuality.Basic, BitMask.Build(Class.RPR), 100); + + def.DefineShared().AddAssociatedActions(AID.ArcaneCircle); + + return def; + } + + public int RedGauge; + public int BlueGauge; + public bool Soulsow; + public float EnshroudLeft; + public int BlueSouls; + public int PurpleSouls; + public float SoulReaver; + public float EnhancedGallows; + public float EnhancedGibbet; + public (float Left, int Stacks) ImmortalSacrifice; + public float BloodsownCircle; + public float IdealHost; + public float EnhancedVoidReaping; + public float EnhancedCrossReaping; + public float EnhancedHarpe; + public float Oblatio; + public float Executioner; + public float PerfectioParata; + + public float TargetDDLeft; + public float ShortestNearbyDDLeft; + + public int NumAOETargets; // melee + public int NumRangedAOETargets; // gluttony, communio + public int NumConeTargets; // grim swathe, guillotine + public int NumLineTargets; // plentiful harvest + + private Actor? BestRangedAOETarget; + private Actor? BestConeTarget; + private Actor? BestLineTarget; + + public enum GCDPriority + { + None = -1, + Soulsow = 1, + EnhancedHarpe = 100, + HarvestMoon = 150, + Filler = 200, + FillerAOE = 201, + SoulSlice = 300, + DDExtend = 600, + Harvest = 650, + Reaver = 700, + Lemure = 700, + Communio = 750, + DDExpiring = 900, + } + + private bool Enshrouded => BlueSouls > 0; + + public override void Exec(StrategyValues strategy, Actor? primaryTarget) + { + SelectPrimaryTarget(strategy, ref primaryTarget, 3); + + var gauge = GetGauge(); + + RedGauge = gauge.Soul; + BlueGauge = gauge.Shroud; + EnshroudLeft = gauge.EnshroudedTimeRemaining * 0.001f; + BlueSouls = gauge.LemureShroud; + PurpleSouls = gauge.VoidShroud; + + Soulsow = Player.FindStatus(SID.Soulsow) != null; + SoulReaver = StatusLeft(SID.SoulReaver); + EnhancedGallows = StatusLeft(SID.EnhancedGallows); + EnhancedGibbet = StatusLeft(SID.EnhancedGibbet); + ImmortalSacrifice = Status(SID.ImmortalSacrifice); + BloodsownCircle = StatusLeft(SID.BloodsownCircle); + IdealHost = StatusLeft(SID.IdealHost); + EnhancedVoidReaping = StatusLeft(SID.EnhancedVoidReaping); + EnhancedCrossReaping = StatusLeft(SID.EnhancedCrossReaping); + EnhancedHarpe = StatusLeft(SID.EnhancedHarpe); + Oblatio = StatusLeft(SID.Oblatio); + Executioner = StatusLeft(SID.Executioner); + PerfectioParata = StatusLeft(SID.PerfectioParata); + + var primaryEnemy = Hints.PotentialTargets.FirstOrDefault(x => x.Actor.InstanceID == primaryTarget?.InstanceID); + + TargetDDLeft = DDLeft(primaryEnemy); + ShortestNearbyDDLeft = float.MaxValue; + + switch (strategy.AOE()) + { + case AOEStrategy.AOE: + case AOEStrategy.ForceAOE: + var nearbyDD = Hints.PriorityTargets.Where(x => Player.DistanceToHitbox(x.Actor) <= 5).Select(DDLeft); + var minNeeded = strategy.AOE() == AOEStrategy.ForceAOE ? 1 : 2; + if (MinIfEnoughElements(nearbyDD.Where(x => x < 30), minNeeded) is float m) + ShortestNearbyDDLeft = m; + break; + } + + NumAOETargets = NumMeleeAOETargets(strategy); + (BestLineTarget, NumLineTargets) = SelectTarget(strategy, primaryTarget, 15, (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 15, 2)); + (BestConeTarget, NumConeTargets) = SelectTarget(strategy, primaryTarget, 8, (primary, other) => Hints.TargetInAOECone(other, Player.Position, 8, Player.DirectionTo(primary), 60.Degrees())); + (BestRangedAOETarget, NumRangedAOETargets) = SelectTarget(strategy, primaryTarget, 25, IsSplashTarget); + + UpdatePositionals(primaryTarget, GetNextPositional(primaryTarget), TrueNorthLeft > GCD); + + OGCD(strategy, primaryTarget); + + if (Soulsow) + PushGCD(AID.HarvestMoon, BestRangedAOETarget, GCDPriority.HarvestMoon); + else if (!Player.InCombat) + PushGCD(AID.SoulSow, Player, GCDPriority.Soulsow); + + if (CountdownRemaining > 0) + { + if (CountdownRemaining < 1.7) + PushGCD(AID.Harpe, primaryTarget); + + return; + } + + if (EnhancedHarpe > GCD) + PushGCD(AID.Harpe, primaryTarget, GCDPriority.EnhancedHarpe); + + DDRefresh(primaryTarget); + + if (SoulReaver > GCD || Executioner > GCD) + { + var gib = Executioner > GCD ? AID.ExecutionersGibbet : AID.Gibbet; + var gal = Executioner > GCD ? AID.ExecutionersGallows : AID.Gallows; + var gui = Executioner > GCD ? AID.ExecutionersGuillotine : AID.Guillotine; + + if (NumConeTargets > 2) + PushGCD(gui, BestConeTarget, GCDPriority.Reaver); + + if (primaryTarget != null) + { + if (EnhancedGallows > GCD) + PushGCD(gal, primaryTarget, GCDPriority.Reaver); + else if (EnhancedGibbet > GCD) + PushGCD(gib, primaryTarget, GCDPriority.Reaver); + else if (GetCurrentPositional(primaryTarget!) == Positional.Rear) + PushGCD(gal, primaryTarget, GCDPriority.Reaver); + else + PushGCD(gib, primaryTarget, GCDPriority.Reaver); + } + } + + if (PerfectioParata > GCD) + PushGCD(AID.Perfectio, BestRangedAOETarget, GCDPriority.Communio); + + EnshroudGCDs(strategy, primaryTarget); + + if (ImmortalSacrifice.Stacks > 0 && BloodsownCircle == 0) + PushGCD(AID.PlentifulHarvest, BestLineTarget, GCDPriority.Harvest); + + // other GCDs are all disabled during enshroud + if (Enshrouded) + return; + + // manually check CD since queue will be delayed in certain circumstances otherwise + if (RedGauge <= 50 && NextChargeIn(AID.SoulSlice) <= GCD) + { + if (NumConeTargets > 2) + PushGCD(AID.SoulScythe, BestConeTarget, GCDPriority.SoulSlice); + + PushGCD(AID.SoulSlice, primaryTarget, GCDPriority.SoulSlice); + } + + if (NumAOETargets > 2) + { + if (ComboLastMove == AID.SpinningScythe) + PushGCD(AID.NightmareScythe, Player, GCDPriority.FillerAOE); + + PushGCD(AID.SpinningScythe, Player, GCDPriority.FillerAOE); + } + + if (ComboLastMove == AID.WaxingSlice) + PushGCD(AID.InfernalSlice, primaryTarget, GCDPriority.Filler); + + if (ComboLastMove == AID.Slice) + PushGCD(AID.WaxingSlice, primaryTarget, GCDPriority.Filler); + + PushGCD(AID.Slice, primaryTarget, GCDPriority.Filler); + } + + private void OGCD(StrategyValues strategy, Actor? primaryTarget) + { + if (primaryTarget == null || !Player.InCombat) + return; + + if (strategy.BuffsOk()) + { + // wait for soul slice in opener + if (RedGauge > 0) + PushOGCD(AID.ArcaneCircle, Player, delay: GCD - 1.6f); + } + + if (NextPositionalImminent && !NextPositionalCorrect) + PushOGCD(AID.TrueNorth, Player, delay: GCD - 0.8f); + + if (SoulReaver > 0 || Executioner > 0) + return; + + if (Oblatio > 0 && (RaidBuffsLeft > 0 || CD(AID.ArcaneCircle) > EnshroudLeft)) + PushOGCD(AID.Sacrificium, BestRangedAOETarget); + + if (PurpleSouls > 1) + { + if (NumConeTargets > 2) + PushOGCD(AID.LemuresScythe, BestConeTarget); + + PushOGCD(AID.LemuresSlice, primaryTarget); + } + + if (ShouldEnshroud(strategy)) + PushOGCD(AID.Enshroud, Player); + + UseSoul(strategy, primaryTarget); + } + + private void DDRefresh(Actor? primaryTarget) + { + void Extend(float timer, AID action, Actor? target) + { + if (!CanFitGCD(timer, CanWeave(AID.Gluttony) ? 2 : 1)) + PushGCD(action, target, GCDPriority.DDExpiring); + + if (timer < 30 && CanWeave(AID.ArcaneCircle, 1) && CD(AID.SoulSlice) > 0) + PushGCD(action, target, GCDPriority.DDExtend); + } + + Extend(ShortestNearbyDDLeft, AID.WhorlofDeath, Player); + Extend(TargetDDLeft, AID.ShadowofDeath, primaryTarget); + } + + private bool ShouldEnshroud(StrategyValues strategy) + { + if (Enshrouded) + return false; + + if (IdealHost > 0) + return true; + + if (BlueGauge < 50) + return false; + + if (RaidBuffsLeft > GCD + 6) + return true; + + if (CanWeave(AID.ArcaneCircle, 2, extraFixedDelay: 1.5f)) // stupid fixed recast timer + return true; + + // TODO tweak deadline, i need a simulator or something + return CD(AID.ArcaneCircle) > 65; + } + + private void UseSoul(StrategyValues strategy, Actor? primaryTarget) + { + // can't + if (RedGauge < 50 || Enshrouded) + return; + + // don't, it would delay Plentiful Harvest + if (ImmortalSacrifice.Stacks > 0 && CanWeave(BloodsownCircle, 0.6f, 1)) + return; + + // don't, we would immediately overwrite Soul Reaver with Perfectio + if (CanFitGCD(PerfectioParata)) + return; + + var debuffLeft = Math.Min(TargetDDLeft, ShortestNearbyDDLeft); + + if (CanFitGCD(debuffLeft, 2)) + PushOGCD(AID.Gluttony, BestRangedAOETarget); + + // can't, we need to refresh debuff first and using sod removes Soul Reaver + if (!CanFitGCD(debuffLeft, 1)) + return; + + // use in raidbuffs + // use to get blue gauge, we need 50 before each 2min window + var spendEarly = RaidBuffsLeft > 0 || BlueGauge < 50; + var gluttonySoon = CanWeave(AID.Gluttony, 5); + + if (RedGauge == 100 || spendEarly && !gluttonySoon) + { + if (NumConeTargets > 2) + PushOGCD(AID.GrimSwathe, BestConeTarget); + + PushOGCD(AID.BloodStalk, primaryTarget); + } + } + + private void EnshroudGCDs(StrategyValues strategy, Actor? primaryTarget) + { + if (BlueSouls == 0) + return; + + if (BlueSouls == 1) + PushGCD(AID.Communio, BestRangedAOETarget, GCDPriority.Communio); + + var prio = GCDPriority.Lemure; + + if (CanWeave(AID.ArcaneCircle, 2, extraFixedDelay: 1.5f) && BlueSouls < 5) + prio = GCDPriority.None; + + if (RaidBuffsLeft > 0) + prio = GCDPriority.Lemure; + + if (NumConeTargets > 2 && prio > 0) + PushGCD(AID.GrimReaping, BestConeTarget, prio + 1); + + PushGCD(EnhancedCrossReaping > GCD ? AID.CrossReaping : AID.VoidReaping, primaryTarget, prio); + } + + protected override float GetCastTime(AID aid) + { + if (aid == AID.Harpe && EnhancedHarpe > GCD) + return 0; + + if (aid == AID.SoulSow && !Player.InCombat) + return 0; + + return base.GetCastTime(aid); + } + + private (Positional, bool) GetNextPositional(Actor? primaryTarget) + { + if (primaryTarget == null || !Unlocked(AID.Gibbet) || NumConeTargets > 2) + return (Positional.Any, false); + + Positional nextPos; + + if (EnhancedGallows > 0) + nextPos = Positional.Rear; + else if (EnhancedGibbet > 0) + nextPos = Positional.Flank; + else + { + var closest = GetCurrentPositional(primaryTarget); + nextPos = closest == Positional.Front ? Positional.Flank : closest; + } + + return (nextPos, SoulReaver > GCD || Executioner > GCD); + } + + private float DDLeft(AIHints.Enemy? target) + => (target?.ForbidDOTs ?? false) + ? float.MaxValue + : StatusDetails(target?.Actor, SID.DeathsDesign, Player.InstanceID, 30).Left; + + private float? MinIfEnoughElements(IEnumerable collection, int minElements) + { + float min = float.MaxValue; + var elements = 0; + foreach (var flt in collection) + { + elements++; + min = MathF.Min(flt, min); + } + + return elements >= minElements ? min : null; + } +} diff --git a/BossMod/Autorotation/xan/Melee/SAM.cs b/BossMod/Autorotation/xan/Melee/SAM.cs index f8804e4b5c..3d1d9404de 100644 --- a/BossMod/Autorotation/xan/Melee/SAM.cs +++ b/BossMod/Autorotation/xan/Melee/SAM.cs @@ -115,12 +115,12 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) OGCD(strategy, primaryTarget); - if (World.Client.CountdownRemaining > 0) + if (CountdownRemaining > 0) { - if (MeikyoLeft == 0 && World.Client.CountdownRemaining < 14) + if (MeikyoLeft == 0 && CountdownRemaining < 14) PushGCD(AID.MeikyoShisui, Player); - if (TrueNorthLeft == 0 && Hints.PotentialTargets.Any(x => !x.Actor.Omnidirectional) && World.Client.CountdownRemaining < 5) + if (TrueNorthLeft == 0 && Hints.PotentialTargets.Any(x => !x.Actor.Omnidirectional) && CountdownRemaining < 5) PushGCD(AID.TrueNorth, Player); return; diff --git a/BossMod/Autorotation/xan/Melee/VPR.cs b/BossMod/Autorotation/xan/Melee/VPR.cs index 746b71d791..441e4bf6cc 100644 --- a/BossMod/Autorotation/xan/Melee/VPR.cs +++ b/BossMod/Autorotation/xan/Melee/VPR.cs @@ -31,7 +31,6 @@ public enum TwinType public int TwinStacks; // max 2, granted by using "coil" or "den" gcds public TwinType TwinCombo; - public float TargetGnashLeft; public float Swiftscaled; public float Instinct; public float FlankstungVenom; @@ -48,8 +47,9 @@ public enum TwinType public float PoisedForTwinblood; public float ReawakenReady; public float ReawakenLeft; + public float HonedReavers; + public float HonedSteel; - public int NumNearbyGnashlessEnemies; public int NumAOETargets; public int NumRangedAOETargets; @@ -57,7 +57,6 @@ public enum TwinType private Actor? BestGenerationTarget; private int CoilMax => Unlocked(TraitID.EnhancedVipersRattle) ? 3 : 2; - private float GnashRefreshTimer => 20; public override void Exec(StrategyValues strategy, Actor? primaryTarget) { @@ -105,9 +104,8 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) PoisedForTwinblood = StatusLeft(SID.PoisedForTwinblood); ReawakenReady = StatusLeft(SID.ReawakenReady); ReawakenLeft = StatusLeft(SID.Reawakened); - - TargetGnashLeft = GnashLeft(primaryTarget); - NumNearbyGnashlessEnemies = Hints.PriorityTargets.Count(x => x.Actor.DistanceToHitbox(Player) <= 5 && GnashLeft(x.Actor) < GnashRefreshTimer); + HonedReavers = StatusLeft(SID.HonedReavers); + HonedSteel = StatusLeft(SID.HonedSteel); (BestRangedAOETarget, NumRangedAOETargets) = SelectTarget(strategy, primaryTarget, 20, IsSplashTarget); BestGenerationTarget = SelectTarget(strategy, primaryTarget, 3, IsSplashTarget).Best; @@ -186,8 +184,8 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) if (NumAOETargets > 2) { - if (ShouldDread(strategy)) - PushGCD(AID.PitOfDread, Player); + if (ShouldVice(strategy)) + PushGCD(AID.Vicepit, Player); if (ComboLastMove is AID.HuntersBite or AID.SwiftskinsBite) { @@ -197,7 +195,7 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) PushGCD(AID.JaggedMaw, Player); } - if (ComboLastMove is AID.SteelMaw or AID.DreadMaw) + if (ComboLastMove is AID.SteelMaw or AID.ReavingMaw) { if (Instinct < Swiftscaled) PushGCD(AID.HuntersBite, Player); @@ -205,15 +203,15 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) PushGCD(AID.SwiftskinsBite, Player); } - if (NumNearbyGnashlessEnemies > 2 && Unlocked(AID.DreadFangs)) - PushGCD(AID.DreadMaw, primaryTarget); + if (HonedSteel == 0 && Unlocked(AID.ReavingFangs)) + PushGCD(AID.ReavingMaw, primaryTarget); PushGCD(AID.SteelMaw, Player); } else { - if (ShouldDread(strategy)) - PushGCD(AID.Dreadwinder, primaryTarget); + if (ShouldVice(strategy)) + PushGCD(AID.Vicewinder, primaryTarget); if (ComboLastMove is AID.HuntersSting) { @@ -231,7 +229,7 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) PushGCD(AID.HindsbaneFang, primaryTarget); } - if (ComboLastMove is AID.SteelFangs or AID.DreadFangs) + if (ComboLastMove is AID.SteelFangs or AID.ReavingFangs) { if (Instinct < Swiftscaled) PushGCD(AID.HuntersSting, primaryTarget); @@ -239,8 +237,8 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) PushGCD(AID.SwiftskinsSting, primaryTarget); } - if (TargetGnashLeft < GnashRefreshTimer && Unlocked(AID.DreadFangs)) - PushGCD(AID.DreadFangs, primaryTarget); + if (HonedSteel == 0 && Unlocked(AID.ReavingFangs)) + PushGCD(AID.ReavingFangs, primaryTarget); PushGCD(AID.SteelFangs, primaryTarget); } @@ -248,7 +246,6 @@ public override void Exec(StrategyValues strategy, Actor? primaryTarget) // fallback for out of range if (Coil > 0) PushGCD(AID.UncoiledFury, BestRangedAOETarget); - } private bool ShouldReawaken(StrategyValues strategy) @@ -256,7 +253,8 @@ private bool ShouldReawaken(StrategyValues strategy) if (!Unlocked(AID.Reawaken) || ReawakenReady == 0 && Offering < 50 || ReawakenLeft > 0 || !strategy.BuffsOk()) return false; - // todo force + if (strategy.Option(SharedTrack.Buffs).As() == OffensiveStrategy.Force) + return true; // full reawaken combo is reawaken (2.2) + generation 1-4 (2s each) = 10.2s (scaled by skill speed) (ouroboros not accounted for since we only really care about casting it with the debuff active) var baseDuration = 8.2f; @@ -265,7 +263,7 @@ private bool ShouldReawaken(StrategyValues strategy) var actual = baseDuration * AttackGCDLength / 2.5f; - if (NumAOETargets == 0 || Instinct < actual || Swiftscaled < actual || TargetGnashLeft < actual || DreadCombo > 0) + if (NumAOETargets == 0 || Instinct < actual || Swiftscaled < actual || DreadCombo > 0) return false; if (RaidBuffsIn > 9000 || RaidBuffsLeft > 10 || ReawakenReady > GCD) @@ -274,20 +272,9 @@ private bool ShouldReawaken(StrategyValues strategy) return Offering == 100 && ComboLastMove is AID.HuntersSting or AID.SwiftskinsSting or AID.HuntersBite or AID.SwiftskinsBite; } - private bool ShouldDread(StrategyValues strategy) - { - if (Swiftscaled <= GCD || DreadCombo > 0) - return false; - - return NumAOETargets > 2 && Unlocked(AID.PitOfDread) - ? NumNearbyGnashlessEnemies > 0 - : TargetGnashLeft < GnashRefreshTimer; - } + private bool ShouldVice(StrategyValues strategy) => Swiftscaled > GCD && DreadCombo == 0 && NextChargeIn(AID.Vicewinder) <= GCD; - private bool ShouldCoil(StrategyValues strategy) - { - return Coil > 1 && TargetGnashLeft > GnashRefreshTimer && Swiftscaled > GCD && DreadCombo == 0; - } + private bool ShouldCoil(StrategyValues strategy) => Coil > 1 && Swiftscaled > GCD && DreadCombo == 0; private void OGCD(StrategyValues strategy, Actor? primaryTarget) { @@ -354,7 +341,7 @@ private void OGCD(StrategyValues strategy, Actor? primaryTarget) var (pos, imm) = getmain(); - if (Anguine > 0 || ShouldReawaken(strategy) || ShouldDread(strategy) || ShouldCoil(strategy)) + if (Anguine > 0 || ShouldReawaken(strategy) || ShouldVice(strategy) || ShouldCoil(strategy)) imm = false; return (pos, imm); @@ -369,6 +356,4 @@ private struct ViperGaugeEx [FieldOffset(0x0B)] public DreadCombo DreadCombo; [FieldOffset(0x10)] public byte ComboEx; // extra combo stuff } - - private float GnashLeft(Actor? a) => StatusDetails(a, SID.NoxiousGnash, Player.InstanceID).Left; } diff --git a/BossMod/BossModule/RaidCooldowns.cs b/BossMod/BossModule/RaidCooldowns.cs index 16a5f57363..303c29f57c 100644 --- a/BossMod/BossModule/RaidCooldowns.cs +++ b/BossMod/BossModule/RaidCooldowns.cs @@ -38,9 +38,19 @@ public float NextDamageBuffIn() return MathF.Max(0, (float)(firstAvailable - _ws.CurrentTime).TotalSeconds); } + public float NextDamageBuffIn2() + { + if (_damageCooldowns.Count == 0) + return float.MaxValue; + + var firstAvailable = _damageCooldowns.Select(e => e.AvailableAt).Min(); + return MathF.Min(float.MaxValue, (float)(firstAvailable - _ws.CurrentTime).TotalSeconds); + } + public static bool IsDamageBuff(uint statusID) => statusID is (uint)AST.SID.Divination or (uint)DRG.SID.BattleLitany or (uint)RPR.SID.ArcaneCircle or (uint)MNK.SID.Brotherhood - or (uint)BRD.SID.BattleVoice or (uint)DNC.SID.TechnicalFinish or (uint)SMN.SID.SearingLight or (uint)RDM.SID.Embolden; + or (uint)BRD.SID.BattleVoice or (uint)DNC.SID.TechnicalFinish or (uint)SMN.SID.SearingLight or (uint)RDM.SID.Embolden + or (uint)PCT.SID.StarryMuse; public float DamageBuffLeft(Actor target) { @@ -77,6 +87,7 @@ private void HandleCast(Actor actor, ActorCastEvent cast) (uint)DNC.AID.QuadrupleTechnicalFinish => UpdateDamageCooldown(actor.InstanceID, cast.Action), // DNC technical finish (uint)SMN.AID.SearingLight => UpdateDamageCooldown(actor.InstanceID, cast.Action), (uint)RDM.AID.Embolden => UpdateDamageCooldown(actor.InstanceID, cast.Action), // RDM embolden + (uint)PCT.AID.StarryMuse => UpdateDamageCooldown(actor.InstanceID, cast.Action), // PCT starry muse (uint)WAR.AID.Interject or (uint)BRD.AID.HeadGraze => UpdateInterruptCooldown(actor.InstanceID, cast.Action, 30), // TODO: PCT _ => false