diff --git a/BossMod/Autorotation/Utility/ClassDRKUtility.cs b/BossMod/Autorotation/Utility/ClassDRKUtility.cs new file mode 100644 index 0000000000..23123397b6 --- /dev/null +++ b/BossMod/Autorotation/Utility/ClassDRKUtility.cs @@ -0,0 +1,69 @@ +namespace BossMod.Autorotation; + +public sealed class ClassDRKUtility(RotationModuleManager manager, Actor player) : RoleTankUtility(manager, player) +{ + public enum Track { DarkMind = SharedTrack.Count, ShadowWall, LivingDead, TheBlackestNight, Oblation, DarkMissionary, Stance } + public enum WallOption { None, ShadowWall, ShadowedVigil } + public enum ForceStanceOption { None, StanceOn, StanceOff } + + public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DRK.AID.DarkForce); + public static readonly ActionID IDStanceApply = ActionID.MakeSpell(DRK.AID.Grit); + public static readonly ActionID IDStanceRemove = ActionID.MakeSpell(DRK.AID.ReleaseGrit); + + public static RotationModuleDefinition Definition() + { + var res = new RotationModuleDefinition("Utility: DRK", "Planner support for utility actions", "Akechi", RotationModuleQuality.Ok, BitMask.Build((int)Class.DRK), 100); + DefineShared(res, IDLimitBreak3, IDStanceApply, IDStanceRemove); + + DefineSimpleConfig(res, Track.DarkMind, "DarkMind", "DMind", 450, DRK.AID.DarkMind, 10); //120s CD, 15s duration + + res.Define(Track.ShadowWall).As("ShadowWall", "Wall", 550) //120s CD, 15s duration + .AddOption(WallOption.None, "None", "Do not use automatically") + .AddOption(WallOption.ShadowWall, "Use", "Use ShadowWall", 120, 15, ActionTargets.Self, 38, 91) + .AddOption(WallOption.ShadowedVigil, "UseEx", "Use ShadowedVigil", 120, 15, ActionTargets.Self, 92) + .AddAssociatedActions(DRK.AID.ShadowWall, DRK.AID.ShadowedVigil); + + DefineSimpleConfig(res, Track.LivingDead, "LivingDead", "LD", 400, DRK.AID.LivingDead, 10); //300s CD, 10s duration + DefineSimpleConfig(res, Track.TheBlackestNight, "The Blackest Night", "TBN", 400, DRK.AID.TheBlackestNight, 7); //15s CD, 7s duration, 3000MP cost + DefineSimpleConfig(res, Track.Oblation, "Oblation", "Obl", 320, DRK.AID.Oblation, 10); //60s CD, 10s duration (TODO: Has Two (2) charges; re-consider better use of both in CDPlanner) + DefineSimpleConfig(res, Track.DarkMissionary, "DarkMissionary", "Mission", 220, DRK.AID.DarkMissionary, 15); //90s CD, 15s duration + + res.Define(Track.Stance).As("Stance", "", 200) //Forcing Stance for CD planning use + .AddOption(ForceStanceOption.None, "None", "Do not use automatically") + .AddOption(ForceStanceOption.StanceOn, "", "Force Stance On", 0, 0, ActionTargets.Self) + .AddOption(ForceStanceOption.StanceOff, "", "Force Stance Off", 0, 0, ActionTargets.Self) + .AddAssociatedActions(DRK.AID.Grit, DRK.AID.ReleaseGrit); + + return res; + } + + public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, float forceMovementIn, bool isMoving) + { + ExecuteShared(strategy, IDLimitBreak3, IDStanceApply, IDStanceRemove, (uint)DRK.SID.Grit, primaryTarget); //Execution of our shared abilities + ExecuteSimple(strategy.Option(Track.DarkMind), DRK.AID.DarkMind, Player); //Execution of DarkMind + ExecuteSimple(strategy.Option(Track.LivingDead), DRK.AID.LivingDead, Player); //Execution of LivingDead + ExecuteSimple(strategy.Option(Track.TheBlackestNight), DRK.AID.TheBlackestNight, Player); //Execution of TheBlackestNight + ExecuteSimple(strategy.Option(Track.Oblation), DRK.AID.Oblation, Player); //Execution of Oblation + ExecuteSimple(strategy.Option(Track.DarkMissionary), DRK.AID.DarkMissionary, Player); //Execution of DarkMissionary + + var wall = strategy.Option(Track.ShadowWall); + var wallAction = wall.As() switch + { + WallOption.ShadowWall => DRK.AID.ShadowWall, + WallOption.ShadowedVigil => DRK.AID.ShadowedVigil, + _ => default + }; + if (wallAction != default) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(wallAction), Player, wall.Priority(), wall.Value.ExpireIn); //Checking proper use of said option + + var stance = strategy.Option(Track.Stance); + var stanceOption = stance.As() switch + { + ForceStanceOption.StanceOn => DRK.AID.Grit, + ForceStanceOption.StanceOff => DRK.AID.ReleaseGrit, + _ => default + }; + if (stanceOption != default) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(stanceOption), Player, stance.Priority()); //Checking proper use of said option + } +} diff --git a/BossMod/Framework/ActionManagerEx.cs b/BossMod/Framework/ActionManagerEx.cs index df7f04cf73..d665cb5eec 100644 --- a/BossMod/Framework/ActionManagerEx.cs +++ b/BossMod/Framework/ActionManagerEx.cs @@ -235,13 +235,7 @@ private bool ExecuteAction(ActionID action, ulong targetId, Vector3 targetPos) { // real action type, just execute our UAL hook // note that for items extraParam should be 0xFFFF (since we want to use any item, not from first inventory slot) - // note that for 'summon carbuncle/eos/titan/ifrit/garuda' actions, extraParam can be used to select glamour - var extraParam = action.Type switch - { - ActionType.Spell => ActionManager.GetExtraParamForSummonAction(action.ID), // will return 0 for non-summon actions - ActionType.Item => 0xFFFFu, - _ => 0u - }; + var extraParam = action.Type == ActionType.Item ? 0xFFFFu : 0; return _inst->UseActionLocation((CSActionType)action.Type, action.ID, targetId, &targetPos, extraParam); } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs index d9af2d8278..986e29ab6f 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs @@ -3,81 +3,79 @@ public enum OID : uint { Boss = 0x4159, // R5.280, x1 - Helper = 0x233C, // R0.500, x4, Helper type - QorrlohTehHelper = 0x43A2, // R0.500, x4 - QorrlohTeh = 0x415A, // R3.000, x0 (spawn during fight) - circle aoe add - RorrlohTeh = 0x415B, // R1.500, x0 (spawn during fight) - rect aoe add - Snowball = 0x415C, // R2.500, x0 (spawn during fight) + Helper = 0x233C, } public enum AID : uint { - AutoAttack = 872, // Boss/Snowball->player, no cast, single-target - FrostingFracas = 36279, // Boss->self, 5.0s cast, single-target, visual (raidwide) - FrostingFracasAOE = 36280, // Helper->self, 5.0s cast, range 60 circle, raidwide - FluffleUp = 36265, // Boss->self, 4.0s cast, single-target, visual (spawn adds) - ColdFeat = 36266, // Boss->self, 4.0s cast, single-target, visual (freeze adds) - IceScream = 36270, // RorrlohTeh->self, 12.0s cast, range 20 width 20 rect - FrozenSwirl = 36271, // QorrlohTeh->self, 12.0s cast, single-target, visual (circle aoe) - FrozenSwirlAOE = 36272, // QorrlohTehHelper->self, 12.0s cast, range 15 circle - Snowscoop = 36275, // Boss->self, 4.0s cast, single-target, visual (spawn snowballs) - SnowBoulder = 36278, // Snowball->self, 4.0s cast, range 50 width 6 rect - SparklingSprinkling = 36713, // Boss->self, 5.0s cast, single-target, visual (spread) - SparklingSprinklingAOE = 36281, // Helper->player, 5.0s cast, range 5 circle spread + FrostingFracas = 36280, // 233C->self, 5.0s cast, range 60 circle + IceScream = 36270, // 415B->self, 12.0s cast, range 20 width 20 rect + FrozenSwirl = 36272, // 43A2->self, 12.0s cast, range 15 circle + SnowBoulder = 36278, // 415C->self, 4.0s cast, range 50 width 6 rect + SparklingSprinkling = 36281, // 233C->player, 5.0s cast, range 5 circle } -public enum IconID : uint +public enum SID : uint { - SparklingSprinkling = 376, // player + Frozen = 3944, // none->_Gen_RorrlohTeh/_Gen_QorrlohTeh1, extra=0x0 + Frozen2 = 3445 } -public enum TetherID : uint +class FrostingFracas(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.FrostingFracas)); + +abstract class FreezableAOEs(BossModule module, ActionID action, AOEShape shape) : Components.GenericAOEs(module) { - Freeze = 272, // RorrlohTeh/QorrlohTeh->Boss -} + protected Dictionary _casters = []; + protected byte _numFrozen; + protected bool _anyCastFinished; -class FrostingFracas(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.FrostingFracasAOE)); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + if (_numFrozen < 2) + yield break; -abstract class FreezableAOEs(BossModule module, ActionID action, AOEShape shape) : Components.GenericAOEs(module, action) -{ - private readonly List _aoes = []; - private int _numFrozen; + foreach ((var caster, var isFrozen) in _casters) + { + var isCastReal = !isFrozen || _anyCastFinished; - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _numFrozen >= 2 ? _aoes.Take(2) : []; + if (isCastReal) + yield return new AOEInstance(shape, caster.Position, caster.Rotation, Module.CastFinishAt(caster.CastInfo, isFrozen ? 8 : 0)); + } + } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - if (spell.Action == WatchedAction) - _aoes.Add(new(shape, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + if (spell.Action == action) + _casters[caster] = false; } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if (spell.Action == WatchedAction && _aoes.Count > 0) + if (spell.Action == action) + if (_casters.Remove(caster)) + _anyCastFinished = true; + + if (_casters.Count == 0) { - _aoes.RemoveAt(0); - if (_aoes.Count == 0) - _numFrozen = 0; + _anyCastFinished = false; + _numFrozen = 0; } } - public override void OnTethered(Actor source, ActorTetherInfo tether) + public override void OnStatusGain(Actor actor, ActorStatus status) { - if (tether.ID == (uint)TetherID.Freeze && _aoes.FindIndex(aoe => aoe.Origin.AlmostEqual(source.Position, 1)) is var index && index >= 0) + if ((SID)status.ID is SID.Frozen or SID.Frozen2 && _casters.ContainsKey(actor)) { - ++_numFrozen; - var aoe = _aoes[index]; - aoe.Activation += TimeSpan.FromSeconds(8); - _aoes.Add(aoe); - _aoes.RemoveAt(index); + _casters[actor] = true; + _numFrozen += 1; } } } -class IceScream(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.IceScream), new AOEShapeRect(20, 10)); -class FrozenSwirl(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.FrozenSwirl), new AOEShapeCircle(15)); // note that helpers that cast visual cast are tethered -class SnowBoulder(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SnowBoulder), new AOEShapeRect(50, 3), 6); -class SparklingSprinkling(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.SparklingSprinklingAOE), 5); +class IceScream(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.IceScream), new AOEShapeRect(20, 10)); +class FrozenSwirl(BossModule module) : FreezableAOEs(module, ActionID.MakeSpell(AID.FrozenSwirl), new AOEShapeCircle(15)); +class SparklingSprinkling(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.SparklingSprinkling), 5); +class SnowBoulder(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SnowBoulder), new AOEShapeRect(50, 3), maxCasts: 6); class D021RyoqorTertehStates : StateMachineBuilder { @@ -87,10 +85,10 @@ public D021RyoqorTertehStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter() + .ActivateOnEnter(); } } -[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12699)] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12699)] public class D021RyoqorTerteh(WorldState ws, Actor primary) : BossModule(ws, primary, new(-108, 119), new ArenaBoundsCircle(20)); diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs index 1587f104a8..7fc677ef82 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs @@ -1,89 +1,69 @@ -namespace BossMod.Dawntrail.Dungeon.D02WorqorZormor.D022Kahderyor; + +namespace BossMod.Dawntrail.Dungeon.D02WorqorZormor.D022Kahderyor; public enum OID : uint { Boss = 0x415D, // R7.000, x1 - Helper = 0x233C, // R0.500, x20, Helper type + Helper = 0x233C, // R0.500, x20, 523 type CrystallineDebris = 0x415E, // R1.400, x0 (spawn during fight) } public enum AID : uint { - AutoAttack = 872, // Boss->player, no cast, single-target - WindUnbound = 36282, // Boss->self, 5.0s cast, range 60 circle, raidwide - CrystallineCrush = 36285, // Boss->location, 5.0+1.0s cast, single-target, visual (tower) - CrystallineCrushAOE = 36153, // Helper->self, 6.3s cast, range 6 circle tower - CrystallineStorm = 36286, // Boss->self, 3.0+1.0s cast, single-target, visual (rects) - CrystallineStormAOE = 36290, // Helper->self, 4.0s cast, range 50 width 2 rect - WindShot = 36284, // Boss->self, 5.5s cast, single-target, visual (donut spreads) - WindShotAOE = 36296, // Helper->player, 6.0s cast, range ?-10 donut spread - WindShotFail = 36300, // Helper->player, no cast, single-target, vuln on player outside safe zone - EarthenShot = 36283, // Boss->self, 5.0+0.5s cast, single-target, visual (circle spreads) - EarthenShotAOE = 36295, // Helper->player, 6.0s cast, range 6 circle spread - SeedCrystals = 36291, // Boss->self, 4.5+0.5s cast, single-target, visual (spread + spawn debris) - SeedCrystalsAOE = 36298, // Helper->player, 5.0s cast, range 6 circle spread - SharpenedSights = 36287, // Boss->self, 3.0s cast, single-target, visual (gaze buff) - EyeOfTheFierce = 36297, // Helper->self, 5.0s cast, range 60 circle gaze - StalagmiteCircle = 36288, // Boss->self, 5.0s cast, single-target, visual (gaze + out) - StalagmiteCircleAOE = 36293, // Helper->self, 5.0s cast, range 15 circle - CyclonicRing = 36289, // Boss->self, 5.0s cast, single-target, visual (gaze + in) - CyclonicRingAOE = 36294, // Helper->self, 5.0s cast, range 8-40 donut + WindUnbound = 36282, // Boss->self, 5.0s cast, range 60 circle + CrystallineCrush = 36153, // 233C->self, 6.3s cast, range 6 circle + WindShot = 36284, // Boss->self, 5.5s cast, single-target + WindShotHelper = 36296, // 233C->player, 6.0s cast, range ?-10 donut + EarthenShot = 36283, // Boss->self, 5.0+0.5s cast, single-target + EarthenShotHelper = 36295, // 233C->player, 6.0s cast, range 6 circle + CrystallineStorm = 36290, // 233C->self, 4.0s cast, range 50 width 2 rect + SeedCrystals = 36298, // 233C->player, 5.0s cast, range 6 circle + CyclonicRing = 36294, // 233C->self, 5.0s cast, range ?-40 donut + EyeOfTheFierce = 36297, // 233C->self, 5.0s cast, range 60 circle + StalagmiteCircle = 36293, // 233C->self, 5.0s cast, range 15 circle } -public enum IconID : uint +class CrystalInout(BossModule module) : Components.GenericAOEs(module) { - CrystallineCrush = 62, // Helper - WindShot = 511, // player - EarthenShot = 169, // player - SeedCrystals = 311, // player -} - -class WindUnbound(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.WindUnbound)); - -class CrystallineCrush(BossModule module) : Components.CastTowers(module, ActionID.MakeSpell(AID.CrystallineCrushAOE), 6, maxSoakers: 4) -{ - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + private enum Active { - if (Towers.Count > 0) - hints.AddForbiddenZone(new AOEShapeDonut(6, 40), Towers[0].Position); + None = 0, + In = 1, + Out = 2 } -} -class CrystallineStorm(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.CrystallineStormAOE), new AOEShapeRect(25, 1, 25)); -class WindShot(BossModule module) : Components.BaitAwayCast(module, ActionID.MakeSpell(AID.WindShotAOE), new AOEShapeDonut(5, 10), true); // TODO: verify inner radius -class EarthenShot(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.EarthenShotAOE), 6); -class CrystalInOut(BossModule module) : Components.GenericAOEs(module) -{ - private enum Mechanic { None, In, Out } - private record struct Source(AOEShape InShape, AOEShape OutShape, WPos Center, Angle Rotation); + private record struct Inout(AOEShape InShape, AOEShape OutShape, WPos Center, Angle Rotation); - private readonly List _sources = []; - private Mechanic _mechanic; - private DateTime _activation; + private DateTime _finishAt; + private readonly List _aoes = []; + private Active _active = Active.None; + private byte _castsWhileActive; - // TODO: verify aoe sizes... - private static readonly AOEShapeCircle _crushIn = new(8); - private static readonly AOEShapeCircle _crushOut = new(15); - private static readonly AOEShapeRect _stormIn = new(25, 1, 25); - private static readonly AOEShapeRect _stormOut = new(25, 7, 25); + private void Reset() + { + _castsWhileActive = 0; + _aoes.Clear(); + _finishAt = default; + } public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_mechanic == Mechanic.None) + if (_active == Active.None) yield break; - foreach (var s in _sources) + foreach (var x in _aoes) { - yield return _mechanic == Mechanic.In - ? new AOEInstance(s.InShape, s.Center, s.Rotation, _activation, ArenaColor.SafeFromAOE, Risky: false) - : new AOEInstance(s.OutShape, s.Center, s.Rotation, _activation); + if (_active == Active.In) + yield return new AOEInstance(x.InShape, x.Center, x.Rotation, _finishAt, ArenaColor.SafeFromAOE, Risky: false); + else + yield return new AOEInstance(x.OutShape, x.Center, x.Rotation, _finishAt); } } public override void AddHints(int slot, Actor actor, TextHints hints) { base.AddHints(slot, actor, hints); - if (_mechanic == Mechanic.In && ActiveAOEs(slot, actor).All(c => !c.Check(actor.Position))) + if (_active == Active.In && ActiveAOEs(slot, actor).All(c => !c.Check(actor.Position))) hints.Add("Get to safe zone!"); } @@ -97,98 +77,105 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme float distance(WPos p) { var dist = shapes.Select(s => s(p)).Min(); - return _mechanic == Mechanic.Out ? dist : -dist; + return _active == Active.Out ? dist : -dist; } - hints.AddForbiddenZone(distance, _activation); + hints.AddForbiddenZone(distance, _finishAt); } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - switch ((AID)spell.Action.ID) + if ((AID)spell.Action.ID == AID.EarthenShotHelper) { - case AID.CrystallineCrushAOE: - _sources.Add(new(_crushIn, _crushOut, caster.Position, spell.Rotation)); - break; - case AID.CrystallineStormAOE: - _sources.Add(new(_stormIn, _stormOut, caster.Position, spell.Rotation)); - break; - case AID.WindShotAOE: - _mechanic = Mechanic.In; - _activation = Module.CastFinishAt(spell); - break; - case AID.EarthenShotAOE: - _mechanic = Mechanic.Out; - _activation = Module.CastFinishAt(spell); - break; + _active = Active.Out; + _finishAt = Module.CastFinishAt(spell); + } + if ((AID)spell.Action.ID == AID.WindShotHelper) + { + _active = Active.In; + _finishAt = Module.CastFinishAt(spell); } } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - switch ((AID)spell.Action.ID) + if ((AID)spell.Action.ID is AID.EarthenShot or AID.WindShot) + _castsWhileActive++; + + if ((AID)spell.Action.ID is AID.EarthenShotHelper or AID.WindShotHelper) { - case AID.WindShot: - case AID.EarthenShot: - ++NumCasts; // we count visual casts, since there's always one per mechanic - break; - case AID.WindShotAOE: - case AID.EarthenShotAOE: - _mechanic = Mechanic.None; - if (NumCasts >= 2) - { - NumCasts = 0; - _sources.Clear(); - _activation = default; - } - break; + _active = Active.None; + if (_castsWhileActive >= 2) + Reset(); } + + if ((AID)spell.Action.ID == AID.CrystallineCrush) + _aoes.Add(new Inout( + new AOEShapeCircle(8), + new AOEShapeCircle(15), + caster.Position, + spell.Rotation + )); + + if ((AID)spell.Action.ID == AID.CrystallineStorm) + _aoes.Add(new Inout( + new AOEShapeRect(25, 1, 25), + new AOEShapeRect(25, 7, 25), + caster.Position, + spell.Rotation + )); } } -class SeedCrystals(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.SeedCrystalsAOE), 6); -class CrystallineDebris(BossModule module) : Components.Adds(module, (uint)OID.CrystallineDebris) +class WindUnbound(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.WindUnbound)); +class WindShot(BossModule module) : Components.BaitAwayCast(module, ActionID.MakeSpell(AID.WindShotHelper), new AOEShapeDonut(5, 10), true); +class EarthenShot(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.EarthenShotHelper), 6, true); +class CrystallineCrush(BossModule module) : Components.CastTowers(module, ActionID.MakeSpell(AID.CrystallineCrush), 6, maxSoakers: 4) { - public override void AddHints(int slot, Actor actor, TextHints hints) - { - if (ActiveActors.Any()) - hints.Add("Break debris!"); - } - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var t in hints.PotentialTargets.Where(e => (OID)e.Actor.OID == OID.CrystallineDebris)) - t.Priority = 1; + if (Towers.Count > 0) + hints.AddForbiddenZone(new AOEShapeDonut(6, 40), Towers[0].Position); } +} +class CrystallineStorm(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.CrystallineStorm), new AOEShapeRect(25, 1, 25)); +class CrystallineDebris(BossModule module) : Components.Adds(module, (uint)OID.CrystallineDebris) +{ public override void DrawArenaForeground(int pcSlot, Actor pc) { - foreach (var c in ActiveActors) + foreach (var c in Actors.Where(x => !x.IsDead)) Arena.AddCircle(c.Position, 1.4f, ArenaColor.Danger); } -} + public override void AddHints(int slot, Actor actor, TextHints hints) + { + if (Actors.Any(x => !x.IsDead)) + hints.Add("Break debris!"); + } +} +class SeedCrystals(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.SeedCrystals), 6); +class CyclonicRing(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.CyclonicRing), new AOEShapeDonut(7.5f, 40)); +class StalagmiteCircle(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.StalagmiteCircle), new AOEShapeCircle(15)); class EyeOfTheFierce(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.EyeOfTheFierce)); -class StalagmiteCircle(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.StalagmiteCircleAOE), new AOEShapeCircle(15)); -class CyclonicRing(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.CyclonicRingAOE), new AOEShapeDonut(8, 40)); class D022KahderyorStates : StateMachineBuilder { public D022KahderyorStates(BossModule module) : base(module) { TrivialPhase() - .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter(); } } -[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12703)] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12703)] public class D022Kahderyor(WorldState ws, Actor primary) : BossModule(ws, primary, new(-53, -57), new ArenaBoundsCircle(20)); diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs index 1aa0a481ab..2d71d74c3f 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs @@ -2,91 +2,80 @@ public enum OID : uint { + Helper = 0x233C, // R0.500, x32, 523 type Boss = 0x415F, // R7.000, x1 - Helper = 0x233C, // R0.500, x32, Helper type - BitingWind = 0x4160, // R1.000, x0 (spawn during fight) AuraSphere = 0x4162, // R1.000, x0 (spawn during fight) + BitingWind = 0x4160, // R1.000, x0 (spawn during fight) } public enum AID : uint { - AutoAttack = 872, // Boss->player, no cast, single-target - HeavingHaymaker = 36269, // Boss->self, 5.0s cast, single-target, visual (raidwide) - HeavingHaymakerAOE = 36375, // Helper->self, 5.3s cast, range 60 circle, raidwide - Stonework = 36301, // Boss->self, 3.0s cast, single-target, visual (elemental square) - LithicImpact = 36302, // Helper->self, 6.8s cast, range 4 width 4 rect (elemental square spawn) + HeavingHaymaker = 36375, // Helper->self, 5.3s cast, range 60 circle + LithicImpact = 36302, // Helper->self, 6.8s cast, range 4 width 4 rect + GreatFlood = 36307, // Helper->self, 7.0s cast, range 80 width 60 rect Allfire1 = 36303, // Helper->self, 7.0s cast, range 10 width 10 rect Allfire2 = 36304, // Helper->self, 8.5s cast, range 10 width 10 rect Allfire3 = 36305, // Helper->self, 10.0s cast, range 10 width 10 rect - VolcanicDrop = 36306, // Helper->player, 5.0s cast, range 6 circle spread - GreatFlood = 36307, // Helper->self, 7.0s cast, range 80 width 60 rect, knock-forward 25 - Sledgehammer = 36313, // Boss->self/players, 5.0s cast, range 60 width 8 rect - SledgehammerRest = 36314, // Boss->self, no cast, range 60 width 8 rect - SledgehammerTargetSelect = 36315, // Helper->player, no cast, single-target, visual (target select) - SledgehammerLast = 39260, // Boss->self, no cast, range 60 width 8 rect - ArcaneStomp = 36319, // Boss->self, 3.0s cast, single-target, visual (spawn buffing spheres) - ShroudOfEons = 36321, // AuraSphere->player, no cast, single-target, damage up from touching spheres - EnduringGlory = 36320, // Boss->self, 6.0s cast, range 60 circle, raidwide after spheres - WindswrathShort = 36310, // Helper->self, 7.0s cast, range 40 circle, knockback 15 - WindswrathLong = 39074, // Helper->self, 15.0s cast, range 40 circle, knockback 15 - Whirlwind = 36311, // Helper->self, no cast, range 5 circle, tornado + VolcanicDrop = 36306, // Helper->player, 5.0s cast, range 6 circle + Sledgehammer = 36313, // Boss->self, 5.0s cast, range 60 width 8 rect + SledgehammerEnd = 39260, // Boss->self, no cast, range 60 width 8 rect + EnduringGlory = 36320, // Boss->self, 6.0s cast, range 60 circle + Windswrath1 = 36310, // Helper->self, 7.0s cast, range 40 circle + Windswrath2 = 39074, // Helper->self, 15.0s cast, range 40 circle } -public enum IconID : uint +class HeavingHaymaker(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.HeavingHaymaker)); +class LithicImpact(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.LithicImpact), new AOEShapeRect(2, 2, 2)); +class GreatFlood(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.GreatFlood), distance: 25, kind: Kind.DirForward) { - VolcanicDrop = 139, // player -} + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + var caster = Casters.FirstOrDefault(); + if (caster?.CastInfo == null) + return; -class HeavingHaymaker(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.HeavingHaymakerAOE)); -class LithicImpact(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.LithicImpact), new AOEShapeRect(2, 2, 2)); + var knockbackTime = Module.CastFinishAt(caster.CastInfo); + + if (IsImmune(slot, knockbackTime)) + return; + + hints.AddForbiddenZone(new AOEShapeRect(20, 20, 5), Arena.Center, caster.Rotation, knockbackTime); + } +} class Allfire(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List[] _aoes = [[], [], []]; - private static readonly AOEShapeRect _shape = new(5, 5, 5); - - public override IEnumerable ActiveAOEs(int slot, Actor actor) - { - var deadline = _aoes.FirstOrDefault().Activation.AddSeconds(0.5f); - return _aoes.TakeWhile(aoe => aoe.Activation < deadline); - } + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes.FirstOrDefault(x => x.Count != 0) ?? []; public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID is AID.Allfire1 or AID.Allfire2 or AID.Allfire3) - { - _aoes.Add(new(_shape, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); - _aoes.SortBy(aoe => aoe.Activation); - } + var castStage = GetStage(spell.Action.ID); + if (castStage >= 0) + _aoes[castStage].Add(new AOEInstance(new AOEShapeRect(5, 5, 5), caster.Position, default, Module.CastFinishAt(spell))); } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID is AID.Allfire1 or AID.Allfire2 or AID.Allfire3) - _aoes.RemoveAll(aoe => aoe.Origin.AlmostEqual(caster.Position, 1)); + var castStage = GetStage(spell.Action.ID); + if (castStage >= 0) + _aoes[castStage].Clear(); } -} -class VolcanicDrop(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.VolcanicDrop), 6); + private int GetStage(uint id) => GetStage((AID)id); -class GreatFlood(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.GreatFlood), 25, kind: Kind.DirForward) -{ - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + private int GetStage(AID id) => id switch { - var caster = Casters.FirstOrDefault(); - if (caster?.CastInfo == null) - return; - - var knockbackTime = Module.CastFinishAt(caster.CastInfo); - - if (IsImmune(slot, knockbackTime)) - return; - - hints.AddForbiddenZone(new AOEShapeRect(20, 20, 5), Arena.Center, caster.Rotation, knockbackTime); - } + AID.Allfire1 => 0, + AID.Allfire2 => 1, + AID.Allfire3 => 2, + _ => -1 + }; } +class VolcanicDrop(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.VolcanicDrop), 6); + class Sledgehammer(BossModule module) : Components.GenericWildCharge(module, 4, ActionID.MakeSpell(AID.Sledgehammer), 60) { public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -101,13 +90,15 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if ((AID)spell.Action.ID == AID.SledgehammerLast) + if ((AID)spell.Action.ID == AID.SledgehammerEnd) Source = null; } } class AuraSpheres : Components.PersistentInvertibleVoidzone { + private bool IsActive => Sources(Module).Any(); + public AuraSpheres(BossModule module) : base(module, 2.5f, m => m.Enemies(OID.AuraSphere).Where(x => !x.IsDead)) { InvertResolveAt = DateTime.MaxValue; @@ -115,13 +106,15 @@ public AuraSpheres(BossModule module) : base(module, 2.5f, m => m.Enemies(OID.Au public override void AddHints(int slot, Actor actor, TextHints hints) { - if (Sources(Module).Any(x => !Shape.Check(actor.Position, x))) + if (!IsActive) + return; + + if (!Sources(Module).Any(x => Shape.Check(actor.Position, x))) hints.Add("Touch the balls!"); } } class EnduringGlory(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.EnduringGlory)); - abstract class Windswrath(BossModule module, ActionID aid) : Components.KnockbackFromCastTarget(module, aid, 15) { public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -138,9 +131,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme hints.AddForbiddenZone(new AOEShapeDonut(5, 60), caster.Position, activation: knockbackTime); } } -class WindswrathShort(BossModule module) : Windswrath(module, ActionID.MakeSpell(AID.WindswrathShort)); -class WindswrathLong(BossModule module) : Windswrath(module, ActionID.MakeSpell(AID.WindswrathLong)); - +class Windswrath1(BossModule module) : Windswrath(module, ActionID.MakeSpell(AID.Windswrath1)); +class Windswrath2(BossModule module) : Windswrath(module, ActionID.MakeSpell(AID.Windswrath2)); class BitingWind(BossModule module) : Components.PersistentVoidzone(module, 4, m => m.Enemies(OID.BitingWind)); class D023GurfurlurStates : StateMachineBuilder @@ -150,17 +142,17 @@ public D023GurfurlurStates(BossModule module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter(); } } -[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12705)] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12705)] public class D023Gurfurlur(WorldState ws, Actor primary) : BossModule(ws, primary, new(-54, -195), new ArenaBoundsSquare(20)); diff --git a/FFXIVClientStructs b/FFXIVClientStructs index 30c3d594ea..b26112506f 160000 --- a/FFXIVClientStructs +++ b/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 30c3d594ea8f97e4a2f6c84cac12c39e45fa85a5 +Subproject commit b26112506fd38490853d38112db8e217d315c7ee