diff --git a/BossMod/Components/BaitAway.cs b/BossMod/Components/BaitAway.cs index 3b14aba3f8..5a82275558 100644 --- a/BossMod/Components/BaitAway.cs +++ b/BossMod/Components/BaitAway.cs @@ -180,6 +180,36 @@ public override void OnUntethered(BossModule module, Actor source, ActorTetherIn } } +// component for mechanics requiring icon targets to bait their aoe away from raid +public class BaitAwayIcon : GenericBaitAway +{ + public AOEShape Shape; + public uint IID; + public float ActivationDelay; + + public virtual Actor? BaitSource(BossModule module, Actor target) => module.PrimaryActor; + + public BaitAwayIcon(AOEShape shape, uint iconID, ActionID aid = default, float activationDelay = 5.1f) : base(aid) + { + Shape = shape; + IID = iconID; + ActivationDelay = activationDelay; + } + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == IID && BaitSource(module, actor) is var source && source != null) + CurrentBaits.Add(new(source, actor, Shape, module.WorldState.CurrentTime.AddSeconds(ActivationDelay))); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + base.OnEventCast(module, caster, spell); + if (spell.Action == WatchedAction) + CurrentBaits.Clear(); + } +} + // component for mechanics requiring cast targets to gtfo from raid (aoe tankbusters etc) public class BaitAwayCast : GenericBaitAway { diff --git a/BossMod/Components/Chains.cs b/BossMod/Components/Chains.cs new file mode 100644 index 0000000000..83be67809c --- /dev/null +++ b/BossMod/Components/Chains.cs @@ -0,0 +1,61 @@ +namespace BossMod.Components; + +// component for breakable chains +public class Chains : CastCounter +{ + public uint TID { get; init; } + public bool TethersAssigned { get; private set; } + private Actor?[] _partner = new Actor?[PartyState.MaxAllianceSize]; + + public Chains(uint tetherID, ActionID aid = default) : base(aid) + { + TID = tetherID; + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (_partner[slot] != null) + hints.Add("Break the tether!"); + } + + public override PlayerPriority CalcPriority(BossModule module, int pcSlot, Actor pc, int playerSlot, Actor player, ref uint customColor) + { + return _partner[pcSlot] == player ? PlayerPriority.Danger : PlayerPriority.Irrelevant; + } + + public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + { + if (_partner[pcSlot] is var partner && partner != null) + arena.AddLine(pc.Position, partner.Position, ArenaColor.Danger); + } + + public override void OnTethered(BossModule module, Actor source, ActorTetherInfo tether) + { + if (tether.ID == TID) + { + TethersAssigned = true; + var target = module.WorldState.Actors.Find(tether.Target); + if (target != null) + { + SetPartner(module, source.InstanceID, target); + SetPartner(module, target.InstanceID, source); + } + } + } + + public override void OnUntethered(BossModule module, Actor source, ActorTetherInfo tether) + { + if (tether.ID == TID) + { + SetPartner(module, source.InstanceID, null); + SetPartner(module, tether.Target, null); + } + } + + private void SetPartner(BossModule module, ulong source, Actor? target) + { + var slot = module.Raid.FindSlot(source); + if (slot >= 0) + _partner[slot] = target; + } +} diff --git a/BossMod/Components/StayMove.cs b/BossMod/Components/StayMove.cs index 900a8fd7b0..c6eed4a5c6 100644 --- a/BossMod/Components/StayMove.cs +++ b/BossMod/Components/StayMove.cs @@ -22,7 +22,7 @@ public override void AddHints(BossModule module, int slot, Actor actor, TextHint hints.Add("Stay!", _lastPositions[slot].prev != _lastPositions[slot].curr || actor.CastInfo != null || actor.TargetID != 0); // note: assume if target is selected, we might autoattack... break; case Requirement.Move: - hints.Add("Move!", _lastPositions[slot].prev == _lastPositions[slot].curr && actor.CastInfo == null); + hints.Add("Move!", _lastPositions[slot].prev == _lastPositions[slot].curr); break; } } diff --git a/BossMod/Modules/Endwalker/Savage/P12S2PallasAthena/Gaiaochos.cs b/BossMod/Modules/Endwalker/Savage/P12S2PallasAthena/Gaiaochos.cs index 1d4d0d8b2a..45ae2ed7d7 100644 --- a/BossMod/Modules/Endwalker/Savage/P12S2PallasAthena/Gaiaochos.cs +++ b/BossMod/Modules/Endwalker/Savage/P12S2PallasAthena/Gaiaochos.cs @@ -11,58 +11,9 @@ class UltimaRay : Components.SelfTargetedAOEs public UltimaRay() : base(ActionID.MakeSpell(AID.UltimaRay), new AOEShapeRect(20, 3)) { } } -class MissingLink : Components.CastCounter +class MissingLink : Components.Chains { - public bool TethersAssigned { get; private set; } - private int[] _partner = Utils.MakeArray(PartyState.MaxPartySize, -1); - - public MissingLink() : base(ActionID.MakeSpell(AID.MissingLink)) { } - - public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) - { - if (_partner[slot] >= 0) - hints.Add("Break the tether!"); - } - - public override PlayerPriority CalcPriority(BossModule module, int pcSlot, Actor pc, int playerSlot, Actor player, ref uint customColor) - { - return _partner[pcSlot] == playerSlot ? PlayerPriority.Danger : PlayerPriority.Irrelevant; - } - - public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) - { - if (module.Raid[_partner[pcSlot]] is var partner && partner != null) - arena.AddLine(pc.Position, partner.Position, ArenaColor.Danger); - } - - public override void OnTethered(BossModule module, Actor source, ActorTetherInfo tether) - { - if (tether.ID == (uint)TetherID.MissingLink) - { - TethersAssigned = true; - var slot1 = module.Raid.FindSlot(source.InstanceID); - var slot2 = module.Raid.FindSlot(tether.Target); - if (slot1 >= 0 && slot2 >= 0) - { - _partner[slot1] = slot2; - _partner[slot2] = slot1; - } - } - } - - public override void OnUntethered(BossModule module, Actor source, ActorTetherInfo tether) - { - if (tether.ID == (uint)TetherID.MissingLink) - { - var slot1 = module.Raid.FindSlot(source.InstanceID); - var slot2 = module.Raid.FindSlot(tether.Target); - if (slot1 >= 0 && slot2 >= 0) - { - _partner[slot1] = -1; - _partner[slot2] = -1; - } - } - } + public MissingLink() : base((uint)TetherID.MissingLink, ActionID.MakeSpell(AID.MissingLink)) { } } class DemiParhelion : Components.SelfTargetedAOEs diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1.cs index 7d0e51ae37..f29d99f761 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1.cs @@ -15,10 +15,24 @@ class MercifulBlooms : Components.SelfTargetedAOEs public MercifulBlooms() : base(ActionID.MakeSpell(AID.MercifulBlooms), new AOEShapeCircle(20)) { } } -// TODO: it's a cleave, target can be determined by icon -class MercifulArc : Components.CastCounter +class MercifulArc : Components.BaitAwayIcon { - public MercifulArc() : base(ActionID.MakeSpell(AID.MercifulArc)) { } + public MercifulArc() : base(new AOEShapeCone(12, 45.Degrees()), (uint)IconID.MercifulArc, ActionID.MakeSpell(AID.MercifulArc)) { } // TODO: verify angle +} + +// TODO: depending on phantom edge, it's either a shared tankbuster cleave or a weird cleave ignoring closest target (?) +class BalefulOnslaught1 : Components.Cleave +{ + public BalefulOnslaught1() : base(ActionID.MakeSpell(AID.BalefulOnslaughtAOE1), new AOEShapeCone(10, 45.Degrees())) { } // TODO: verify angle +} +class BalefulOnslaught2 : Components.Cleave +{ + public BalefulOnslaught2() : base(ActionID.MakeSpell(AID.BalefulOnslaughtAOE2), new AOEShapeCone(10, 45.Degrees())) { } // TODO: verify angle +} + +class BurningChains : Components.Chains +{ + public BurningChains() : base((uint)TetherID.BurningChains, ActionID.MakeSpell(AID.ScorchingShackle)) { } } // TODO: it's a line stack, but I don't think there's a way to determine cast target - so everyone should just stack?.. @@ -32,6 +46,11 @@ class IronRose : Components.SelfTargetedAOEs public IronRose() : base(ActionID.MakeSpell(AID.IronRose), new AOEShapeRect(50, 4)) { } } +class DeadIron : Components.BaitAwayTethers +{ + public DeadIron() : base(new AOEShapeCone(50, 15.Degrees()), (uint)TetherID.DeadIron, ActionID.MakeSpell(AID.DeadIronAOE)) { DrawTethers = false; } +} + [ModuleInfo(GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9834)] public class DRS1 : BossModule { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1Enums.cs index dbe1d7a540..5c2fa20371 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1Enums.cs @@ -40,7 +40,7 @@ public enum AID : uint BalefulSwathe = 23248, // Boss->self, no cast, single-target, visual (side aoes) BalefulSwatheAOE = 23249, // Helper->self, no cast, range 50 ?-degree cone (doesn't really look like cone...) BalefulOnslaught = 23690, // Boss->self, 4.0s cast, single-target, visual (tankbuster, shareable or skipping closest target) - BalefulOnslaughtAOE1 = 23253, // Boss->self, no cast, range 10 ?-degree cone tankbuster (shareable/invulable) + BalefulOnslaughtAOE1 = 23253, // Boss->self, no cast, range 10 ?-degree cone tankbuster (shareable/invulnable) BalefulOnslaughtAOE2 = 23254, // Boss->self, no cast, range 10 ?-degree cone tankbuster (solo, skipping closest target) PhantomEdge = 23229, // Boss->self, 4.0s cast, single-target, visual (applies status changing some effects) ScorchingShackle = 23243, // Helper->self, no cast, ??? (happens if chains aren't broken in time) @@ -79,6 +79,13 @@ public enum SID : uint public enum TetherID : uint { - //_Gen_Tether_128 = 128, // player->player + BurningChains = 128, // player->player DeadIron = 138, // player->SeekerAvatar }; + +public enum IconID : uint +{ + BurningChains = 238, // player + DeadIron = 237, // player + MercifulArc = 243, // player +}; diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1States.cs index 955aeed3ee..82003333db 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DRS1States.cs @@ -1,4 +1,6 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS1TrinitySeeker; +using System.Xml.Linq; + +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS1TrinitySeeker; class DRS1States : StateMachineBuilder { @@ -16,52 +18,60 @@ public DRS1States(BossModule module) : base(module) private void Phase1(uint id) { - VerdantTempest(id, 6.2f); + VerdantTempest(id, 6.1f); MercyFourfoldSeasonsOfMercy(id + 0x10000, 4.9f); - // TODO: verdant tempest -> merciful arc -> forced phase change - SimpleState(id + 0xFF0000, 100, "???"); + VerdantTempest(id + 0x20000, 2.3f); + MercifulArc(id + 0x30000, 10); // TODO: never seen this one, delay unknown + SimpleState(id + 0x40000, 10, "Next phase"); // TODO: never seen this one, delay unknown } private void Phase2(uint id) { VerdantPathSword(id, 0); BalefulOnslaughtDouble(id + 0x10000, 4.2f); - BurningChainsBalefulBlade(id + 0x20000, 13.6f); + BurningChainsBalefulBlade(id + 0x20000, 13.4f); BalefulFirestormBalefulBlade(id + 0x30000, 2.3f); - // TODO: verdant tempest -> forced phase change - SimpleState(id + 0xFF0000, 100, "???"); + VerdantTempest(id + 0x40000, 4.1f); + SimpleState(id + 0x50000, 5.5f, "Next phase"); } private void Phase3(uint id) { VerdantPathFist(id, 0); - IronRoseIronSplitter(id + 0x10000, 5.3f); + IronRoseIronSplitter(id + 0x10000, 5.4f); IronSplitterDeadIronIronRose(id + 0x20000, 5.5f); - // TODO: verdant tempest -> forced phase change - SimpleState(id + 0xFF0000, 100, "???") - .ActivateOnEnter(); + VerdantTempest(id + 0x30000, 6.1f); + SimpleState(id + 0x40000, 5.5f, "Next phase"); } private void Phase4(uint id) { VerdantPathKatana(id, 0); BalefulFirestormMercyFourfoldSeasonsOfMercy(id + 0x10000, 7.5f); - VerdantTempest(id + 0x20000, 8.3f); - MercifulArc(id + 0x30000, 10.5f); - MercyFourfoldIronSplitter(id + 0x40000, 4.6f); + VerdantTempest(id + 0x20000, 8.4f, true); + MercifulArc(id + 0x30000, 5.4f); + MercyFourfoldIronSplitter(id + 0x40000, 4.5f); SeasonsOfMercyIronSplitterIronRose(id + 0x50000, 4.7f); VerdantTempest(id + 0x60000, 5.2f); + VerdantPathSword(id + 0x70000, 9.5f); - // TODO: mercy (clone) + baleful blade + chains -> verdant tempest -> iron splitter + baleful blade -> mercy (clone) + chains + baleful blade -> baleful onslaught -> verdant path (katana) -> enrage + BalefulBladeMercyFourfold(id + 0x80000, 4.4f); + VerdantTempest(id + 0x90000, 8.5f, true); + IronSplitterBalefulBlade(id + 0xA0000, 2.8f); + BurningChainsMercyFourfoldBalefulBlade(id + 0xB0000, 6.5f); + // baleful onslaught -> verdant path (katana) -> enrage SimpleState(id + 0xFF0000, 100, "???"); } - private void VerdantTempest(uint id, float delay) + private void VerdantTempest(uint id, float delay, bool withChains = false) { - Cast(id, AID.VerdantTempest, delay, 5) + CastStart(id, AID.VerdantTempest, delay) + .ActivateOnEnter(withChains); + CastEnd(id + 1, 5) .ActivateOnEnter(); - ComponentCondition(id + 2, 0.7f, comp => comp.NumCasts > 0, "Raidwide") + ComponentCondition(id + 2, 0.7f, comp => comp.NumCasts > 0, withChains ? "Chains + Raidwide" : "Raidwide") .DeactivateOnExit() + .DeactivateOnExit(withChains) .SetHint(StateMachine.StateHint.Raidwide); } @@ -124,8 +134,9 @@ private void MercyFourfoldSeasonsOfMercy(uint id, float delay) private void MercifulArc(uint id, float delay) { - ComponentCondition(id, delay, comp => comp.NumCasts > 0, "Cleave") - .ActivateOnEnter() + ComponentCondition(id, delay, comp => comp.CurrentBaits.Count > 0) + .ActivateOnEnter(); + ComponentCondition(id + 1, 5.1f, comp => comp.NumCasts > 0, "Cleave") .DeactivateOnExit() .SetHint(StateMachine.StateHint.Tankbuster); } @@ -138,35 +149,47 @@ private void VerdantPathSword(uint id, float delay) .DeactivateOnExit(); } - // TODO: components for cleaves, figure out how to make optional casts... private void BalefulOnslaughtDouble(uint id, float delay) { - Cast(id, AID.BalefulOnslaught, delay, 4, "Tankbuster (shared/invuln)"); - // +0.2s: resolve - Cast(id + 0x10, AID.PhantomEdge, 3.4f, 4); - Cast(id + 0x20, AID.BalefulOnslaught, 2.2f, 4, "Tankbuster (solo)"); - // +0.2s: resolve + Cast(id, AID.BalefulOnslaught, delay, 4) + .ActivateOnEnter(); + ComponentCondition(id + 2, 0.2f, comp => comp.NumCasts > 0, "Tankbuster (shared/invuln)") + .DeactivateOnExit() + .SetHint(StateMachine.StateHint.Tankbuster); + + Cast(id + 0x10, AID.PhantomEdge, 3.2f, 4); + + Cast(id + 0x20, AID.BalefulOnslaught, 2.2f, 4) + .ActivateOnEnter(); + ComponentCondition(id + 0x22, 0.2f, comp => comp.NumCasts > 0, "Tankbuster (solo)") + .DeactivateOnExit() + .SetHint(StateMachine.StateHint.Tankbuster); } - // TODO: component for chains - private void BurningChainsBalefulBlade(uint id, float delay) + // this handles preceeding optional phantom edge cast - the delay doesn't seem to be affected + private State BalefulBladeCastStart(uint id, float delay) { // note: there could be an extra phantom edge cast (7.3 to 3.3 before next cast start), but it doesn't change the baleful blade delay - Condition(id, delay, () => (Module.PrimaryActor.CastInfo?.IsSpell() ?? false) && (AID)Module.PrimaryActor.CastInfo!.Action.ID is AID.BalefulBlade1 or AID.BalefulBlade2, "", 10000); // this is a hack for phantom edge... - CastMulti(id + 0x10, new[] { AID.BalefulBlade1, AID.BalefulBlade2 }, 0, 8, "Knockback") + return Condition(id, delay, () => (Module.PrimaryActor.CastInfo?.IsSpell() ?? false) && (AID)Module.PrimaryActor.CastInfo!.Action.ID is AID.BalefulBlade1 or AID.BalefulBlade2, maxOverdue: 10000) + .SetHint(StateMachine.StateHint.BossCastStart); + } + + private void BurningChainsBalefulBlade(uint id, float delay) + { + BalefulBladeCastStart(id, delay) + .ActivateOnEnter(); + CastEnd(id + 1, 8, "Chains + Knockback") .ActivateOnEnter() - .DeactivateOnExit(); + .DeactivateOnExit() + .DeactivateOnExit(); // resolve ~2.8s into cast } private void BalefulFirestormBalefulBlade(uint id, float delay) { Cast(id, AID.ManifestAvatar, delay, 3); - // +6.2s: optional phantom edge start - // +6.7s: first comet (and then next every 1s after) - // +10.2s: optional phantom edge end - Condition(id + 0x10, 13.5f, () => (Module.PrimaryActor.CastInfo?.IsSpell() ?? false) && (AID)Module.PrimaryActor.CastInfo!.Action.ID is AID.BalefulBlade1 or AID.BalefulBlade2, "", 10000) // this is a hack for phantom edge... + BalefulBladeCastStart(id + 0x10, 13.5f) .ActivateOnEnter(); // first comet happens 6.8s before, then every second; first firestorm starts right before this cast - CastMulti(id + 0x20, new[] { AID.BalefulBlade1, AID.BalefulBlade2 }, 0, 8, "Dashes + Knockback") + CastEnd(id + 0x11, 8, "Dashes + Knockback") .ActivateOnEnter() .DeactivateOnExit() .DeactivateOnExit(); // last firestorm ends ~1.1s before cast end @@ -187,7 +210,7 @@ private void IronRoseIronSplitter(uint id, float delay) .ActivateOnEnter(); ComponentCondition(id + 0x11, 3.5f, comp => comp.NumCasts > 0, "Line AOEs") .DeactivateOnExit(); - Cast(id + 0x20, AID.IronSplitter, 0.6f, 5, "Tiles/sands") + Cast(id + 0x20, AID.IronSplitter, 0.8f, 5, "Tiles/sands") .ActivateOnEnter() .DeactivateOnExit(); } @@ -226,7 +249,6 @@ private void BalefulFirestormMercyFourfoldSeasonsOfMercy(uint id, float delay) MercyFourfold(id + 0x1000, 11.4f, true) .DeactivateOnExit(); // last firestorm ends ~4.2s before 4th mercy cast end SeasonsOfMercy(id + 0x2000, 1.4f); - // TODO: chains (icons ~0.7s before blooms resolve, tethers ~3.3s after blooms, resolve ~6.4s after blooms) } private void MercyFourfoldIronSplitter(uint id, float delay) @@ -236,13 +258,13 @@ private void MercyFourfoldIronSplitter(uint id, float delay) MercyFourfoldHints(id + 0x100, 4.2f); CastStart(id + 0x200, AID.MercyFourfold, 0.2f); - ComponentCondition(id + 0x210, 0.5f, comp => comp.NumCasts > 0, "Tiles/sands 1") + ComponentCondition(id + 0x210, 0.5f, comp => comp.NumCasts > 0, "Tiles/sands 1", 2) // note: very large variance here .DeactivateOnExit(); CastEnd(id + 0x220, 1.5f) .ActivateOnEnter(); // splitter cast starts ~2.5s after mercy fourfold cast start, during resolve MercyFourfoldResolve(id + 0x300, 0.2f, false); - ComponentCondition(id + 0x400, 1.5f, comp => comp.NumCasts > 0, "Tiles/sands 2") + ComponentCondition(id + 0x400, 1.5f, comp => comp.NumCasts > 0, "Tiles/sands 2", 2) // note: very large variance here .DeactivateOnExit(); } @@ -250,9 +272,9 @@ private void SeasonsOfMercyIronSplitterIronRose(uint id, float delay) { CastStart(id, AID.SeasonsOfMercy, delay) .ActivateOnEnter(); // splitter starts ~1.3s before seasons cast start - ComponentCondition(id + 0x10, 3.6f, comp => comp.NumCasts > 0, "Tiles/sands") + ComponentCondition(id + 0x10, 4, comp => comp.NumCasts > 0, "Tiles/sands", 2) // note: very large variance here .DeactivateOnExit(); - CastEnd(id + 0x20, 1.4f) + CastEnd(id + 0x20, 1) .ActivateOnEnter(); // orb appears right before cast end ComponentCondition(id + 0x30, 2, comp => comp.Casters.Count > 0) .ActivateOnEnter(); @@ -268,7 +290,47 @@ private void SeasonsOfMercyIronSplitterIronRose(uint id, float delay) .DeactivateOnExit(); ComponentCondition(id + 0x80, 2.0f, comp => comp.NumCasts > 0, "Line AOEs") .DeactivateOnExit(); - ComponentCondition(id + 0x90, 1.4f, comp => comp.NumCasts > 0, "Bloom") + ComponentCondition(id + 0x90, 1.3f, comp => comp.NumCasts > 0, "Bloom") .DeactivateOnExit(); } + + private void BalefulBladeMercyFourfold(uint id, float delay) + { + Cast(id, AID.ManifestAvatar, delay, 3); + BalefulBladeCastStart(id + 0x10, 13.4f) + .ActivateOnEnter(); // avatar starts first mercy cast ~3.1s before this + CastEnd(id + 0x11, 8, "Knockback") + .ActivateOnEnter() + .DeactivateOnExit(); + + MercyFourfoldResolve(id + 0x100, 3.9f, false); + } + + private void IronSplitterBalefulBlade(uint id, float delay) + { + Cast(id, AID.ManifestAvatar, delay, 3); + BalefulBladeCastStart(id + 0x10, 12.5f) + .ActivateOnEnter(); // avatar starts iron splitter cast ~0.8s before baleful blade + ComponentCondition(id + 0x20, 4.2f, comp => comp.NumCasts > 0, "Tiles/sands", 2) // note: very large variance here + .ActivateOnEnter() + .DeactivateOnExit(); + CastEnd(id + 0x30, 3.8f, "Knockback") + .DeactivateOnExit(); + } + + // TODO: haven't seen the full mechanic... + private void BurningChainsMercyFourfoldBalefulBlade(uint id, float delay) + { + Cast(id, AID.ManifestAvatar, delay, 3); + ComponentCondition(id + 0x10, 9.3f, comp => comp.AOEs.Count > 0) + .ActivateOnEnter(); + // TODO: no idea about what happens after... + BalefulBladeCastStart(id + 0x20, 20) // this happens while fourfold is being resolved.... + .ActivateOnEnter(); + CastEnd(id + 0x21, 8, "Chains + Mercies + Knockback ...") + .ActivateOnEnter() + .DeactivateOnExit() + .DeactivateOnExit() + .DeactivateOnExit(); + } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DeadIron.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DeadIron.cs deleted file mode 100644 index d69bba6e34..0000000000 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/DeadIron.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS1TrinitySeeker; - -// TODO: generalize to earthshaker component -class DeadIron : Components.GenericAOEs -{ - private List<(Actor target, Actor source, DateTime activation)> _earthshakers = new(); - - private static readonly AOEShapeCone _shape = new(50, 15.Degrees()); - - public DeadIron() : base(ActionID.MakeSpell(AID.DeadIronAOE), "GTFO from earthshaker!") { } - - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - foreach (var e in _earthshakers) - if (e.target != actor) - yield return new(_shape, e.source.Position, Angle.FromDirection(e.target.Position - e.source.Position), e.activation); - } - - public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) - { - base.AddHints(module, slot, actor, hints, movementHints); - - var ownSource = _earthshakers.Find(e => e.target == actor).source; - if (ownSource != null) - { - var dir = Angle.FromDirection(actor.Position - ownSource.Position); - hints.Add("GTFO from others!", module.Raid.WithoutSlot().Any(a => a != actor && _shape.Check(a.Position, ownSource.Position, dir))); - } - } - - public override PlayerPriority CalcPriority(BossModule module, int pcSlot, Actor pc, int playerSlot, Actor player, ref uint customColor) - { - return _earthshakers.Any(e => e.target == player) ? PlayerPriority.Interesting : PlayerPriority.Irrelevant; - } - - public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) - { - var ownSource = _earthshakers.Find(e => e.target == pc).source; - if (ownSource != null) - _shape.Outline(arena, ownSource.Position, Angle.FromDirection(pc.Position - ownSource.Position)); - } - - public override void OnTethered(BossModule module, Actor source, ActorTetherInfo tether) - { - // tether is from player (tether source == earthshaker target) to avatar (tether target == earthshaker source) - if (tether.ID == (uint)TetherID.DeadIron && module.WorldState.Actors.Find(tether.Target) is var target && target != null) - _earthshakers.Add((source, target, module.WorldState.CurrentTime.AddSeconds(4.6f))); - } -} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/MercyFourfold.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/MercyFourfold.cs index cc9ab0995d..2cde3127f7 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/MercyFourfold.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS1TrinitySeeker/MercyFourfold.cs @@ -2,8 +2,8 @@ class MercyFourfold : Components.GenericAOEs { - private List _aoes = new(); - private List _safezones = new(); + public readonly List AOEs = new(); + private readonly List _safezones = new(); private static readonly AOEShapeCone _shapeAOE = new(50, 90.Degrees()); private static readonly AOEShapeCone _shapeSafe = new(50, 45.Degrees()); @@ -11,8 +11,8 @@ public MercyFourfold() : base(ActionID.MakeSpell(AID.MercyFourfoldAOE)) { } public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - if (_aoes.Count > 0) - yield return _aoes[0]; + if (AOEs.Count > 0) + yield return AOEs[0]; if (_safezones.Count > 0 && _safezones[0] != null) yield return _safezones[0]!.Value; } @@ -34,18 +34,18 @@ public override void OnStatusGain(BossModule module, Actor actor, ActorStatus st return; var dir = actor.Rotation + dirOffset; - if (_aoes.Count > 0) + if (AOEs.Count > 0) { // see whether there is a safezone for two contiguous aoes - var mid = dir.ToDirection() + _aoes.Last().Rotation.ToDirection(); // length should be either ~sqrt(2) or ~0 + var mid = dir.ToDirection() + AOEs.Last().Rotation.ToDirection(); // length should be either ~sqrt(2) or ~0 if (mid.LengthSq() > 1) _safezones.Add(new(_shapeSafe, actor.Position, Angle.FromDirection(-mid), new(), ArenaColor.SafeFromAOE, false)); else _safezones.Add(null); } - var activationDelay = 15 - 1.3f * _aoes.Count; - _aoes.Add(new(_shapeAOE, actor.Position, dir, module.WorldState.CurrentTime.AddSeconds(activationDelay))); + var activationDelay = 15 - 1.3f * AOEs.Count; + AOEs.Add(new(_shapeAOE, actor.Position, dir, module.WorldState.CurrentTime.AddSeconds(activationDelay))); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) @@ -53,8 +53,8 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent base.OnEventCast(module, caster, spell); if (spell.Action == WatchedAction) { - if (_aoes.Count > 0) - _aoes.RemoveAt(0); + if (AOEs.Count > 0) + AOEs.RemoveAt(0); if (_safezones.Count > 0) _safezones.RemoveAt(0); } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2States.cs deleted file mode 100644 index da48927f45..0000000000 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2States.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS2Dahu; - -class DRS2States : StateMachineBuilder -{ - public DRS2States(BossModule module) : base(module) - { - DeathPhase(0, SinglePhase); - } - - private void SinglePhase(uint id) - { - ReverberatingRoarHotChargeFirebreathe(id, 10.3f); - HeadDownSpitFlameShockwave(id + 0x10000, 14.6f); - FeralHowl(id + 0x20000, 1.5f); - FirebreatheRotating(id + 0x30000, 1.6f); - // TODO: adds (no need for a state?) -> roar + head down -> shockwave -> hysteric assault -> rotating firebreathe -> hot charge + firebreathe -> ... - SimpleState(id + 0xFF0000, 100, "???"); - } - - private void ReverberatingRoarHotChargeFirebreathe(uint id, float delay) - { - ComponentCondition(id, delay, comp => comp.Casters.Count > 0, "Rocks 1 bait") - .ActivateOnEnter(); - Cast(id + 0x10, AID.HotCharge, 9.5f, 3, "Charge 1") - .ActivateOnEnter() - .DeactivateOnExit(); - Cast(id + 0x20, AID.HotCharge, 1.9f, 3, "Charge 2") - .ActivateOnEnter() - .DeactivateOnExit() - .DeactivateOnExit(); // last rock ends ~1.1s before cast start - Cast(id + 0x30, AID.Firebreathe, 1.9f, 5, "Cone") - .ActivateOnEnter() - .DeactivateOnExit(); - } - - private void HeadDownSpitFlameShockwave(uint id, float delay) - { - ComponentCondition(id, delay, comp => comp.Casters.Count > 0, "Add charges begin") - .ActivateOnEnter(); - CastStart(id + 0x10, AID.SpitFlame, 4.8f) - .ActivateOnEnter(); // first icon appears right before cast start - CastEnd(id + 0x11, 8); - ComponentCondition(id + 0x20, 3.7f, comp => !comp.Active, "Spits resolve") - .DeactivateOnExit(); - CastMulti(id + 0x30, new[] { AID.LeftSidedShockwaveFirst, AID.RightSidedShockwaveFirst }, 3.2f, 3, "Shockwave 1") - .ActivateOnEnter(); - CastMulti(id + 0x40, new[] { AID.LeftSidedShockwaveSecond, AID.RightSidedShockwaveSecond }, 1.6f, 1, "Shockwave 2") - .DeactivateOnExit(); - ComponentCondition(id + 0x50, 1.1f, comp => comp.Casters.Count == 0, "Add charges resolve") - .DeactivateOnExit(); - } - - private void FeralHowl(uint id, float delay) - { - Cast(id, AID.FeralHowl, delay, 5) - .ActivateOnEnter() - .ActivateOnEnter(); - ComponentCondition(id + 2, 2.1f, comp => comp.NumCasts > 0, "Knockback") - .DeactivateOnExit(); - ComponentCondition(id + 3, 1.5f, comp => comp.NumCasts > 0, "Add aoes") - .DeactivateOnExit(); - } - - private void FirebreatheRotating(uint id, float delay) - { - CastStart(id, AID.FirebreatheRotating, delay) - .ActivateOnEnter(); // icon appears just before cast start - CastEnd(id + 1, 5); - ComponentCondition(id + 0x10, 0.7f, comp => comp.NumCasts > 0, "Cone 1"); - ComponentCondition(id + 0x20, 8, comp => comp.NumCasts >= 5, "Cone 5") - .DeactivateOnExit(); - } -} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/FeralHowl.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/FeralHowl.cs deleted file mode 100644 index 51a8896b53..0000000000 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/FeralHowl.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS2Dahu; - -class FeralHowl : Components.Knockback -{ - public FeralHowl() : base(ActionID.MakeSpell(AID.FeralHowlAOE), true) { } - - public override IEnumerable Sources(BossModule module, int slot, Actor actor) - { - // TODO: not all the wall is safe... - yield return new(module.Bounds.Center, Math.Max(0.1f, module.Bounds.HalfSize - 0.1f - (actor.Position - module.Bounds.Center).Length())); - } -} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/FirebreatheRotating.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/FirebreatheRotating.cs deleted file mode 100644 index 26d242ced8..0000000000 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/FirebreatheRotating.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS2Dahu; - -class FirebreatheRotating : Components.GenericAOEs -{ - private Angle _increment; - private Angle _nextRotation; - private DateTime _nextActivation; - private static readonly AOEShapeCone _shape = new(60, 45.Degrees()); - - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (_increment != default && _nextActivation != default) - { - if (NumCasts < 5) - yield return new(_shape, module.PrimaryActor.Position, _nextRotation, _nextActivation, ArenaColor.Danger); - if (NumCasts < 4) - yield return new(_shape, module.PrimaryActor.Position, _nextRotation + _increment, _nextActivation.AddSeconds(2)); - } - } - - public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID == AID.FirebreatheRotating) - { - _nextRotation = spell.Rotation; - _nextActivation = spell.NPCFinishAt.AddSeconds(0.7f); - } - } - - public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) - { - if ((AID)spell.Action.ID == AID.FirebreatheRotatingAOE) - { - NumCasts++; - _nextRotation += _increment; - } - } - - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) - { - var angle = (IconID)iconID switch - { - IconID.FirebreatheCW => -90.Degrees(), - IconID.FirebreatheCCW => 90.Degrees(), - _ => default - }; - if (angle != default) - _increment = angle; - } -} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3.cs similarity index 75% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3.cs index 65b3769d3b..b7d7ae6af5 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS2Dahu; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3Dahu; class FallingRock : Components.LocationTargetedAOEs { @@ -25,10 +25,15 @@ class HuntersClaw : Components.SelfTargetedAOEs public HuntersClaw() : base(ActionID.MakeSpell(AID.HuntersClaw), new AOEShapeCircle(8)) { } } +class Burn : Components.BaitAwayIcon +{ + public Burn() : base(new AOEShapeCircle(30), (uint)IconID.Burn, ActionID.MakeSpell(AID.Burn), 8.2f) { CenterAtTarget = true; } +} + [ModuleInfo(GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9751)] -public class DRS2 : BossModule +public class DRS3 : BossModule { - public DRS2(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(82, 138), 30)) { } + public DRS3(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(82, 138), 30)) { } protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3Enums.cs similarity index 81% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2Enums.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3Enums.cs index 8b0912abcb..0b19db4caa 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/DRS2Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3Enums.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS2Dahu; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3Dahu; public enum OID : uint { @@ -23,11 +23,14 @@ public enum AID : uint RightSidedShockwaveFirst = 22384, // Boss->self, 3.0s cast, range 15 180-degree cone LeftSidedShockwaveSecond = 22385, // Boss->self, 1.0s cast, range 15 180-degree cone RightSidedShockwaveSecond = 22386, // Boss->self, 1.0s cast, range 15 180-degree cone - FeralHowl = 22375, // Boss->self, 5.0s cast, single-target + FeralHowl = 22375, // Boss->self, 5.0s cast, single-target, visual (knockback) FeralHowlAOE = 23349, // Helper->self, no cast, range 50 circle, raidwide knockback 30 HuntersClaw = 22377, // Marchosias->self, 8.5s cast, range 8 circle puddle FirebreatheRotating = 22379, // Boss->self, 5.0s cast, single-target, visual (rotating cone) FirebreatheRotatingAOE = 22380, // Boss->self, 0.5s cast, range 60 90-degree cone + HystericAssault = 22392, // Boss->self, 5.0s cast, single-target, visual (knockback) + HystericAssaultAOE = 23363, // Helper->self, no cast, range 50 circle, raidwide knockback 30 + Burn = 21603, // Helper->players, no cast, range 45 circle with ? falloff }; public enum IconID : uint @@ -38,4 +41,5 @@ public enum IconID : uint SpitFlame4 = 82, // player FirebreatheCW = 167, // Boss FirebreatheCCW = 168, // Boss + Burn = 87, // player }; diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3States.cs new file mode 100644 index 0000000000..86b68494c2 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/DRS3States.cs @@ -0,0 +1,121 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3Dahu; + +class DRS3States : StateMachineBuilder +{ + public DRS3States(BossModule module) : base(module) + { + DeathPhase(0, SinglePhase); + } + + private void SinglePhase(uint id) + { + ReverberatingRoarHotChargeFirebreathe(id, 10.3f); + HeadDownSpitFlameShockwaveFeralHowl(id + 0x10000, 14.5f); + FirebreatheRotating(id + 0x20000, 1.6f); + CrownedMarchosias(id + 0x30000, 7.4f); + ReverberatingRoarHeadDownShockwaveSpitFlameHystericAssault(id + 0x40000, 37); + FirebreatheRotating(id + 0x50000, 6.1f, true); + // TODO: shockwave? -> hot charge + firebreathe -> ... + SimpleState(id + 0xFF0000, 100, "???"); + } + + private void Shockwave(uint id, float delay) + { + CastMulti(id, new[] { AID.LeftSidedShockwaveFirst, AID.RightSidedShockwaveFirst }, delay, 3, "Shockwave 1") + .ActivateOnEnter(); + CastMulti(id + 0x10, new[] { AID.LeftSidedShockwaveSecond, AID.RightSidedShockwaveSecond }, 1.6f, 1, "Shockwave 2") + .DeactivateOnExit(); + } + + private State SpitFlame(uint id, float delay) + { + CastStart(id, AID.SpitFlame, delay) + .ActivateOnEnter(); // first icon appears right before cast start + CastEnd(id + 1, 8); + return ComponentCondition(id + 0x10, 3.7f, comp => !comp.Active, "Spits resolve") + .DeactivateOnExit(); + } + + private void ReverberatingRoarHotChargeFirebreathe(uint id, float delay) + { + ComponentCondition(id, delay, comp => comp.Casters.Count > 0, "Rocks 1 bait") + .ActivateOnEnter(); + Cast(id + 0x10, AID.HotCharge, 9.5f, 3, "Charge 1") // note: large variance + .ActivateOnEnter() + .DeactivateOnExit(); + Cast(id + 0x20, AID.HotCharge, 1.8f, 3, "Charge 2") + .ActivateOnEnter() + .DeactivateOnExit() + .DeactivateOnExit(); // last rock ends ~1.1s before cast start + Cast(id + 0x30, AID.Firebreathe, 1.8f, 5, "Cone") + .ActivateOnEnter() + .DeactivateOnExit(); + } + + private void HeadDownSpitFlameShockwaveFeralHowl(uint id, float delay) + { + ComponentCondition(id, delay, comp => comp.Casters.Count > 0, "Add charges begin") + .ActivateOnEnter(); + SpitFlame(id + 0x100, 4.8f); + Shockwave(id + 0x200, 3.2f); + ComponentCondition(id + 0x300, 1, comp => comp.Casters.Count == 0, "Add charges resolve", 5) // if one of the spit flame target dies, shockwave happens a bit earlier + .DeactivateOnExit(); + + Cast(id + 0x400, AID.FeralHowl, 1.6f, 5) + .ActivateOnEnter() + .ActivateOnEnter(); + ComponentCondition(id + 0x410, 2.1f, comp => comp.NumCasts > 0, "Knockback") + .DeactivateOnExit(); + ComponentCondition(id + 0x420, 1.4f, comp => comp.NumCasts > 0, "Add aoes") + .DeactivateOnExit(); + } + + private void FirebreatheRotating(uint id, float delay, bool withHeadDown = false) + { + CastStart(id, AID.FirebreatheRotating, delay) + .ActivateOnEnter(); // icon appears just before cast start + CastEnd(id + 1, 5) + .ActivateOnEnter(withHeadDown); + ComponentCondition(id + 0x10, 0.7f, comp => comp.NumCasts > 0, "Cone 1"); + ComponentCondition(id + 0x11, 2, comp => comp.NumCasts > 1); + ComponentCondition(id + 0x12, 2, comp => comp.NumCasts > 2); + ComponentCondition(id + 0x13, 2, comp => comp.NumCasts > 3); + ComponentCondition(id + 0x14, 2, comp => comp.NumCasts > 4, "Cone 5") + .DeactivateOnExit() + .DeactivateOnExit(withHeadDown); + } + + private void CrownedMarchosias(uint id, float delay) + { + Condition(id, delay, () => Module.Enemies(OID.CrownedMarchosias).Any(add => add.IsTargetable), "Adds appear"); + // +5.2s: second set + // +10.0s: third set, first set gets damage up + // +14.1s: first set gets second stack of damage up + // and so on... + } + + private void ReverberatingRoarHeadDownShockwaveSpitFlameHystericAssault(uint id, float delay) + { + ComponentCondition(id, delay, comp => comp.Casters.Count > 0, "Rocks 1 bait") + .ActivateOnEnter(); + ComponentCondition(id + 1, 0.2f, comp => comp.Casters.Count > 0, "Add charges begin") + .ActivateOnEnter(); + Shockwave(id + 0x100, 3.9f); + SpitFlame(id + 0x200, 2.5f) + .DeactivateOnExit() // last rocks end ~1.1s before cast start + .DeactivateOnExit(); // last charges end ~2.5s after cast start + + ComponentCondition(id + 0x300, 1.0f, comp => comp.CurrentBaits.Count > 0) + .ActivateOnEnter(); + + Cast(id + 0x400, AID.HystericAssault, 0.1f, 5) + .ActivateOnEnter() + .ActivateOnEnter(); + ComponentCondition(id + 0x310, 0.9f, comp => comp.NumCasts > 0, "Knockback") + .DeactivateOnExit(); + ComponentCondition(id + 0x320, 2.2f, comp => comp.NumCasts > 0, "Flares") + .DeactivateOnExit(); + ComponentCondition(id + 0x330, 0.4f, comp => comp.NumCasts > 0, "Add aoes") + .DeactivateOnExit(); + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/FeralHowlHystericAssault.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/FeralHowlHystericAssault.cs new file mode 100644 index 0000000000..db1ba0ce2b --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/FeralHowlHystericAssault.cs @@ -0,0 +1,27 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3Dahu; + +// these two abilities are very similar, only differ by activation delay and action id +class FeralHowlHystericAssault : Components.Knockback +{ + private AID _aidCast; + private float _delay; + private Source? _source; + + public FeralHowlHystericAssault(AID aidCast, AID aidAOE, float delay) : base(ActionID.MakeSpell(aidAOE), true) + { + _aidCast = aidCast; + _delay = delay; + StopAtWall = true; // TODO: not all the wall is safe... + } + + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_source); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == _aidCast) + _source = new(caster.Position, 30, spell.NPCFinishAt.AddSeconds(_delay)); + } +} + +class FeralHowl() : FeralHowlHystericAssault(AID.FeralHowl, AID.FeralHowlAOE, 2.1f) { } +class HystericAssault() : FeralHowlHystericAssault(AID.HystericAssault, AID.HystericAssaultAOE, 0.9f) { } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/FirebreatheRotating.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/FirebreatheRotating.cs new file mode 100644 index 0000000000..2f5770d399 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/FirebreatheRotating.cs @@ -0,0 +1,36 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3Dahu; + +class FirebreatheRotating : Components.GenericRotatingAOE +{ + private Angle _increment; + + private static readonly AOEShapeCone _shape = new(60, 45.Degrees()); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.FirebreatheRotating) + { + Sequences.Add(new(_shape, caster.Position, spell.Rotation, _increment, spell.NPCFinishAt.AddSeconds(0.7f), 2, 5)); + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.FirebreatheRotatingAOE && Sequences.Count > 0) + { + AdvanceSequence(0, module.WorldState.CurrentTime); + } + } + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + var angle = (IconID)iconID switch + { + IconID.FirebreatheCW => -90.Degrees(), + IconID.FirebreatheCCW => 90.Degrees(), + _ => default + }; + if (angle != default) + _increment = angle; + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/Shockwave.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/Shockwave.cs similarity index 94% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/Shockwave.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/Shockwave.cs index c7e36701a3..473e7d5d2e 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/Shockwave.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/Shockwave.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS2Dahu; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3Dahu; class Shockwave : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/SpitFlame.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/SpitFlame.cs similarity index 87% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/SpitFlame.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/SpitFlame.cs index f8783d9417..e60b18938a 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS2Dahu/SpitFlame.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3Dahu/SpitFlame.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS2Dahu; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3Dahu; class SpitFlame : Components.UniformStackSpread { @@ -12,6 +12,12 @@ public override void Init(BossModule module) _adds = module.Enemies(OID.Marchosias); } + public override void Update(BossModule module) + { + Spreads.RemoveAll(s => s.Target.IsDead); // if target dies after being marked, cast will be skipped + base.Update(module); + } + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) { base.AddHints(module, slot, actor, hints, movementHints); diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4.cs deleted file mode 100644 index 58dbc2b077..0000000000 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4Phantom; - -class MaledictionOfAgony : Components.CastCounter -{ - public MaledictionOfAgony() : base(ActionID.MakeSpell(AID.MaledictionOfAgonyAOE)) { } -} - -[ModuleInfo(GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9755)] -public class DRS4 : BossModule -{ - public DRS4(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(202, -370), 24)) { } -} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/AboveBoard.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/AboveBoard.cs similarity index 98% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/AboveBoard.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/AboveBoard.cs index d49392f56b..a7c66845fa 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/AboveBoard.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/AboveBoard.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; class AboveBoard : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/CoatOfArms.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/CoatOfArms.cs similarity index 89% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/CoatOfArms.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/CoatOfArms.cs index 30c9fa8806..95ec040bc3 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/CoatOfArms.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/CoatOfArms.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; class CoatOfArms : Components.DirectionalParry { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4.cs similarity index 90% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4.cs index e3600a799e..6554f153e7 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; class OptimalPlaySword : Components.SelfTargetedAOEs { @@ -36,6 +36,11 @@ class IcyPortent : Components.CastHint public IcyPortent() : base(ActionID.MakeSpell(AID.IcyPortent), "Move!") { } } +class PawnOff : Components.SelfTargetedAOEs +{ + public PawnOff() : base(ActionID.MakeSpell(AID.PawnOffReal), new AOEShapeCircle(20)) { } +} + // TODO: consider showing reflect hints class Fracture : Components.CastCounter { @@ -43,7 +48,7 @@ public Fracture() : base(ActionID.MakeSpell(AID.Fracture)) { } } [ModuleInfo(PrimaryActorOID = (uint)OID.Knight, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9838)] -public class DRS3 : BossModule +public class DRS4 : BossModule { private IReadOnlyList _warrior; private IReadOnlyList _soldier; @@ -57,7 +62,7 @@ public class DRS3 : BossModule public IReadOnlyList AuraSpheres; public IReadOnlyList SpiritualSpheres; - public DRS3(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(244, -162), 25)) + public DRS4(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(244, -162), 25)) { _warrior = Enemies(OID.Warrior); _soldier = Enemies(OID.Soldier); diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4Enums.cs similarity index 97% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3Enums.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4Enums.cs index 4f3a5b9221..82baa0001a 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4Enums.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; public enum OID : uint { @@ -87,6 +87,8 @@ public enum AID : uint SecretsRevealed = 23407, // Soldier->self, 5.0s cast, single-target, visual (tether 2 of 4 avatars that will be activated) SecretsRevealedExtra = 23408, // SoldierAvatar->self, no cast, single-target, visual (untether after secrets revealed?) AvatarJump = 22584, // SoldierAvatar->location, no cast, single-target, teleport + PawnOffReal = 22585, // SoldierAvatar->self, 7.0s cast, range 20 circle aoe + PawnOffFake = 22586, // SoldierAvatar->self, 7.0s cast, range 20 circle fake aoe SpitefulSpirit = 22574, // Warrior->self, 5.0s cast, single-target, visual (summon spheres?) StrongpointDefense = 22558, // Knight->self, 5.0s cast, single-target, visual (summon wards?) @@ -100,6 +102,7 @@ public enum AID : uint Burst = 23446, // SpiritualSphere->self, no cast, range 60 circle, raidwide if no one soaked the sphere EnrageP1Knight = 22563, // Knight->self, 10.0s cast, wipe (starts when warrior is killed) + EnrageP1Warrior = 22579, // Warrior->self, 10.0s cast, wipe (starts when knight is killed) EnrageP2Soldier = 22595, // Soldier->self, 10.0s cast, wipe (starts when gunner is killed) EnrageP2Gunner = 22614, // Gunner->self, 10.0s cast, wipe (starts when soldier is killed) EnrageP3Knight = 22793, // Knight->self, 70.0s cast diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4States.cs similarity index 94% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3States.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4States.cs index 08e2e6df3f..e70e881c9c 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/DRS3States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/DRS4States.cs @@ -1,10 +1,10 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; -class DRS3States : StateMachineBuilder +class DRS4States : StateMachineBuilder { - DRS3 _module; + DRS4 _module; - public DRS3States(DRS3 module) : base(module) + public DRS4States(DRS4 module) : base(module) { _module = module; SimplePhase(0, Phase0, "P0: 4 guards") @@ -27,7 +27,7 @@ private void Phase0(uint id) private void Phase1(uint id) { ActorTargetable(id, _module.Knight, true, 3.3f, "Warrior + Knight appear"); - Phase1Repeat(id + 0x100000, 8.2f); + Phase1Repeat(id + 0x100000, 8.1f); Phase1Repeat(id + 0x200000, 8.1f); // TODO: enrage SimpleState(id + 0xFF0000, 100, "???"); @@ -37,7 +37,7 @@ private void Phase1Repeat(uint id, float delay) { P1SelectVulnerability(id, delay); P1OptimalOffensiveAboveBoard(id + 0x10000, 9.1f); - P1WindsOfWeightOptimalPlay(id + 0x20000, 10.4f); + P1WindsOfWeightOptimalPlay(id + 0x20000, 10.3f); P1RapidSever(id + 0x30000, 4.2f); } @@ -112,8 +112,8 @@ private void P1OptimalOffensiveAboveBoard(uint id, float delay) // +1.0s: create 2x6 bombs (aetherial bolt/burst) ActorCastStart(id + 0x20, _module.Warrior, AID.ReversalOfForces, 3.2f); // tethers/icons for reversal appear ~0.1s before cast start - ActorTargetable(id + 0x21, _module.Knight, false, 2.9f, "Knight disappear"); - ActorCastEnd(id + 0x22, _module.Warrior, 1.1f); + ActorTargetable(id + 0x21, _module.Knight, false, 3, "Knight disappear"); + ActorCastEnd(id + 0x22, _module.Warrior, 1); // +0.7s: create aetherial sphere in center // +0.9s: replace tethers with statuses // +2.2s: tether sphere to knight @@ -136,12 +136,12 @@ private void P1OptimalOffensiveAboveBoard(uint id, float delay) ComponentCondition(id + 0x40, 0.9f, comp => comp.CurState == AboveBoard.State.ThrowUpDone, "Throw up") .DeactivateOnExit(); // cast finishes 0.2s or 1.2s before that ComponentCondition(id + 0x50, 2.1f, comp => comp.CurState == AboveBoard.State.ShortExplosionsDone, "Bombs 1"); - ActorTargetable(id + 0x60, _module.Knight, true, 1.3f, "Knight reappear"); - ComponentCondition(id + 0x70, 2.9f, comp => comp.CurState == AboveBoard.State.LongExplosionsDone, "Bombs 2") + ActorTargetable(id + 0x60, _module.Knight, true, 1.4f, "Knight reappear"); + ComponentCondition(id + 0x70, 2.8f, comp => comp.CurState == AboveBoard.State.LongExplosionsDone, "Bombs 2") .DeactivateOnExit(); ActorCast(id + 0x1000, _module.Warrior, AID.Boost, 5.0f, 4, false, "Damage up"); - P1BloodAndBone(id + 0x1010, 3); + P1BloodAndBone(id + 0x1010, 3.1f); } private void P1WindsOfWeightOptimalPlay(uint id, float delay) @@ -151,10 +151,10 @@ private void P1WindsOfWeightOptimalPlay(uint id, float delay) // +0.9s: replace tethers with statuses ActorCastStart(id + 0x20, _module.Warrior, AID.WindsOfWeight, 3.2f); - ActorCastStartMulti(id + 0x21, _module.Knight, new[] { AID.SwordOmen, AID.ShieldOmen }, 2) + ActorCastStartMulti(id + 0x21, _module.Knight, new[] { AID.SwordOmen, AID.ShieldOmen }, 1.9f) .ActivateOnEnter(); ActorCastEnd(id + 0x22, _module.Knight, 3); - ActorCastEnd(id + 0x23, _module.Warrior, 1, false, "Wind/gravity") + ActorCastEnd(id + 0x23, _module.Warrior, 1.1f, false, "Wind/gravity") .DeactivateOnExit(); ActorCastStartMulti(id + 0x30, _module.Knight, new[] { AID.OptimalPlaySword, AID.OptimalPlayShield }, 2.1f); @@ -236,7 +236,6 @@ private void P2GunTurret(uint id, float delay) .DeactivateOnExit(); } - // TODO: explosions component - never seen it so far... private void P2DoubleGambit(uint id, float delay) { ActorCast(id, _module.Soldier, AID.RelentlessBatterySoldier, delay, 5); // both soldier and gunner cast their visual @@ -251,11 +250,13 @@ private void P2DoubleGambit(uint id, float delay) ActorCast(id + 0x40, _module.Gunner, AID.QueensShotUnseen, 3.1f, 7, false, "Face gunner") .ActivateOnEnter() + .ActivateOnEnter() // casts start ~0.4s into gunner's cast .DeactivateOnExit(); // +0.5s: turrets start their casts ComponentCondition(id + 0x50, 3.5f, comp => comp.NumCasts > 0, "Face turret") .ActivateOnEnter() - .DeactivateOnExit(); + .DeactivateOnExit() + .DeactivateOnExit(); } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/GreatBallOfFire.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/GreatBallOfFire.cs similarity index 95% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/GreatBallOfFire.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/GreatBallOfFire.cs index 085820ca06..1950d24108 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/GreatBallOfFire.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/GreatBallOfFire.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; class GreatBallOfFire : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/OptimalOffensive.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/OptimalOffensive.cs similarity index 96% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/OptimalOffensive.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/OptimalOffensive.cs index d4049a7eec..62c8cd4e86 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/OptimalOffensive.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/OptimalOffensive.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; class OptimalOffensiveSword : Components.ChargeAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/SpellforgeSteelstingHint.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/SpellforgeSteelstingHint.cs similarity index 93% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/SpellforgeSteelstingHint.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/SpellforgeSteelstingHint.cs index df49ccfee9..c63d4b31cc 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/SpellforgeSteelstingHint.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/SpellforgeSteelstingHint.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; // TODO: improve hints (check player's class; for healers, hints for party members having incorrect buff) class SpellforgeSteelstingHint : BossComponent diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/WindsOfWeight.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/WindsOfWeight.cs similarity index 95% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/WindsOfWeight.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/WindsOfWeight.cs index f29b45e74e..d0bb595a1a 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS3QueensGuard/WindsOfWeight.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4QueensGuard/WindsOfWeight.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS3QueensGuard; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; class WindsOfWeight : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5.cs new file mode 100644 index 0000000000..bb04160e83 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5.cs @@ -0,0 +1,22 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5Phantom; + +class MaledictionOfAgony : Components.CastCounter +{ + public MaledictionOfAgony() : base(ActionID.MakeSpell(AID.MaledictionOfAgonyAOE)) { } +} + +class BloodyWraith : Components.Adds +{ + public BloodyWraith() : base((uint)OID.BloodyWraith) { } +} + +class MistyWraith : Components.Adds +{ + public MistyWraith() : base((uint)OID.MistyWraith) { } +} + +[ModuleInfo(GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9755)] +public class DRS5 : BossModule +{ + public DRS5(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(202, -370), 24)) { } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5Enums.cs similarity index 78% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4Enums.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5Enums.cs index 1245cc3450..412b5712bc 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5Enums.cs @@ -1,8 +1,10 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4Phantom; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5Phantom; public enum OID : uint { Boss = 0x30AE, // R2.400, x1 + BloodyWraith = 0x30B0, // R2.000, spawn during fight + MistyWraith = 0x30B1, // R2.000, spawn during fight Helper = 0x233C, // R0.500, x18 MiasmaLowRect = 0x1EB0DD, // R0.500, EventObj type, spawn during fight MiasmaLowCircle = 0x1EB0DE, // R0.500, EventObj type, spawn during fight @@ -26,4 +28,7 @@ public enum AID : uint LingeringMiasmaRest = 22455, // Helper->location, 1.0s cast, range 8 circle SwirlingMiasmaFirst = 22456, // Helper->location, 10.0s cast, range 5-19 donut SwirlingMiasmaRest = 22457, // Helper->location, 1.0s cast, range 5-19 donut + Transference = 22445, // Boss->location, no cast, single-target, teleport + Summon = 22464, // Boss->self, 3.0s cast, single-target, visual (go untargetable and spawn adds) + MaledictionOfRuin = 22465, // Boss->self, 43.0s cast, single-target }; diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5States.cs similarity index 70% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4States.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5States.cs index dbc055e239..2aa2c9570d 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/DRS4States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/DRS5States.cs @@ -1,8 +1,8 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4Phantom; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5Phantom; -class DRS4States : StateMachineBuilder +class DRS5States : StateMachineBuilder { - public DRS4States(BossModule module) : base(module) + public DRS5States(BossModule module) : base(module) { DeathPhase(0, SinglePhase); } @@ -12,6 +12,7 @@ private void SinglePhase(uint id) MaledictionOfAgony(id, 7.1f); ManipulateInvertMiasma(id + 0x10000, 4.5f); ManipulateInvertMiasma(id + 0x20000, 3.0f); + SummonMaledictionOfRuin(id + 0x30000, 0.4f); // TODO: summon + malediction of ruin > miasma + knockback > vile wave > ice spikes > excruciation > malediction of agony > repeat? SimpleState(id + 0xFF0000, 100, "???"); } @@ -48,4 +49,21 @@ private void ManipulateInvertMiasma(uint id, float delay) ComponentCondition(id + 0x20, 22.2f, comp => comp.NumLanesFinished >= 8, "Miasma resolve") .DeactivateOnExit(); } + + private void SummonMaledictionOfRuin(uint id, float delay) + { + Cast(id, AID.Summon, delay, 3); + Targetable(id + 0x10, false, 1.0f, "Boss disappears"); + ComponentCondition(id + 0x20, 3, comp => comp.ActiveActors.Any(), "Adds appear") // 2x bloody + 1x misty + .ActivateOnEnter() + .ActivateOnEnter() + .SetHint(StateMachine.StateHint.DowntimeEnd); + + CastStart(id + 0x30, AID.MaledictionOfRuin, 2.1f); + // +5.7s: second set of adds created (2x bloody + 2x misty) + // +8.3s: second set of adds targetable + // +17.7s: third set of adds created (3x bloody + 3x misty) + // +20.6s: third set of adds targetable + Timeout(id + 0x40, 43, "Adds resolve"); + } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/Miasma.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/Miasma.cs similarity index 98% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/Miasma.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/Miasma.cs index 649a161a84..f5b6165359 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS4Phantom/Miasma.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5Phantom/Miasma.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4Phantom; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5Phantom; // TODO: improve hints, currently they are not good... we probably don't need a fully generic implementation, since there are few possible patterns class Miasma : Components.GenericAOEs diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/AllegiantArsenal.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/AllegiantArsenal.cs similarity index 97% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/AllegiantArsenal.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/AllegiantArsenal.cs index 161eefd40b..a6f372e564 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/AllegiantArsenal.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/AllegiantArsenal.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; class AllegiantArsenal : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/BladeOfEntropy.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/BladeOfEntropy.cs similarity index 97% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/BladeOfEntropy.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/BladeOfEntropy.cs index 2c609133f9..9b3ff49977 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/BladeOfEntropy.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/BladeOfEntropy.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; // note: instead of trying to figure out cone intersections and shit, we use the fact that clones are always positioned on grid and just check each cell class BladeOfEntropy : TemperatureAOE diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/Bow.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/Bow.cs similarity index 99% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/Bow.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/Bow.cs index f43e4ee38d..906cd5b6f4 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/Bow.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/Bow.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; // aoe starts at cast and ends with envcontrol; it's not considered 'risky' when paired with quick march class FlamesOfBozja : Components.GenericAOEs diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6.cs similarity index 61% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6.cs index dd0903b4d5..4d46d45178 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6.cs @@ -1,4 +1,14 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; + +class WrathOfBozja : Components.CastSharedTankbuster +{ + public WrathOfBozja() : base(ActionID.MakeSpell(AID.WrathOfBozja), new AOEShapeCone(60, 45.Degrees())) { } // TODO: verify angle +} + +class WrathOfBozjaBow : Components.CastSharedTankbuster +{ + public WrathOfBozjaBow() : base(ActionID.MakeSpell(AID.WrathOfBozjaBow), new AOEShapeCone(60, 45.Degrees())) { } // TODO: verify angle +} // note: it is combined with different AOEs (bow1, bow2, staff1) class QuickMarch : Components.StatusDrivenForcedMarch @@ -22,7 +32,7 @@ class GleamingArrow : Components.SelfTargetedAOEs } [ModuleInfo(GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9853)] -public class DRS5 : BossModule +public class DRS6 : BossModule { - public DRS5(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(-272, -82), 25)) { } + public DRS6(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(-272, -82), 25)) { } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6Enums.cs similarity index 93% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5Enums.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6Enums.cs index 6a1e74ffb2..72278d6c3d 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6Enums.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; public enum OID : uint { @@ -93,13 +93,19 @@ public enum AID : uint BladeOfEntropyAC22 = 22877, // Helper->self, 10.0s cast, range 40 180-degree cone ElementalBrandSword = 23473, // Boss->self, 3.0s cast, single-target, visual (applies brand debuffs) - UnseenEye = 23476, // Boss->self, 3.0s cast, single-target, visual (show clones for crisscross aoe) + UnseenEyeBow = 23476, // Boss->self, 3.0s cast, single-target, visual (show clones for crisscross aoe) + UnseenEyeStaff = 22912, // Boss->self, 3.0s cast, single-target, visual (show clones for crisscross aoe) GleamingArrow = 22861, // AvowedAvatar->self, 6.0s cast, range 60 width 10 rect aoe ApplyHotBrand1 = 22837, // Helper->self, no cast, range 60 circle, converts brand to temperature ApplyHotBrand2 = 22838, // Helper->self, no cast, range 60 circle, converts brand to temperature ApplyColdBrand1 = 22839, // Helper->self, no cast, range 60 circle, converts brand to temperature ApplyColdBrand2 = 22840, // Helper->self, no cast, range 60 circle, converts brand to temperature ClearTemperatures = 23332, // Helper->self, no cast, range 60 circle, visual (clear temperatures?) + + Enrage = 22868, // Boss->self, 12.0s cast, range 85 circle, enrage + EnrageSecondary = 23355, // Helper->self, 12.6s cast, range 85 circle, enrage hitting second half of raid + EnrageRepeat = 22869, // Boss->self, no cast, range 85 circle, enrage repeat in 3s + EnrageRepeatSecondary = 23356, // Helper->self, 0.6s cast, range 85 circle, secondary enrage repeat in 3s }; public enum SID : uint diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6States.cs similarity index 74% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5States.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6States.cs index 2736ab1a30..71f99ca944 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/DRS5States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/DRS6States.cs @@ -1,8 +1,8 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; -class DRS5States : StateMachineBuilder +class DRS6States : StateMachineBuilder { - public DRS5States(BossModule module) : base(module) + public DRS6States(BossModule module) : base(module) { DeathPhase(0, SinglePhase) .ActivateOnEnter(); @@ -10,7 +10,7 @@ public DRS5States(BossModule module) : base(module) private void SinglePhase(uint id) { - WrathOfBozja(id, 8.5f); + WrathOfBozja(id, 7.4f, false); GloryOfBozja(id + 0x10000, 3.2f); AllegiantArsenalAOE(id + 0x20000, 6.3f); AllegiantArsenalAOE(id + 0x30000, 5.2f); @@ -34,7 +34,7 @@ private void ForkStaffSwordBow(uint id) Staff2(id + 0x300000, 8); Sword2(id + 0x400000, 8); Bow2(id + 0x500000, 8); - SimpleState(id + 0xFF0000, 10, "Enrage"); // TODO: action + Enrage(id + 0x600000, 16); } private void ForkBowSwordStaff(uint id) @@ -45,18 +45,18 @@ private void ForkBowSwordStaff(uint id) Bow2(id + 0x300000, 9.6f); Sword2(id + 0x400000, 8); Staff2(id + 0x500000, 8); - SimpleState(id + 0xFF0000, 10, "Enrage"); // TODO: action + Enrage(id + 0x600000, 16); // TODO: timing } private void ForkSwordBowStaff(uint id) { Sword1(id, 5.3f); Bow1(id + 0x100000, 8.6f); - Staff1(id + 0x200000, 7.5f); + Staff1(id + 0x200000, 8); // note: very high variance here... Sword2(id + 0x300000, 7.4f); - Bow2(id + 0x400000, 8.5f); - Staff2(id + 0x500000, 8); // TODO: timing - SimpleState(id + 0xFF0000, 10, "Enrage"); // TODO: action + Bow2(id + 0x400000, 8.6f); + Staff2(id + 0x500000, 8.4f); + Enrage(id + 0x600000, 16); // TODO: timing } private void ForkStaffBowSword(uint id) @@ -68,42 +68,41 @@ private void ForkStaffBowSword(uint id) Staff2(id + 0x300000, 8); Bow2(id + 0x400000, 8); Sword2(id + 0x500000, 8); - SimpleState(id + 0xFF0000, 10, "Enrage"); // TODO: action + Enrage(id + 0x600000, 16); // TODO: timing } private void ForkSwordStaffBow(uint id) { - // TODO: no idea about timings here - Sword1(id, 8); - Staff1(id + 0x100000, 8); - Bow1(id + 0x200000, 8); - Sword2(id + 0x300000, 8); - Staff2(id + 0x400000, 8); - Bow2(id + 0x500000, 8); - SimpleState(id + 0xFF0000, 10, "Enrage"); // TODO: action + Sword1(id, 5.2f); + Staff1(id + 0x100000, 7.6f); + Bow1(id + 0x200000, 8.9f); + Sword2(id + 0x300000, 7.7f); + Staff2(id + 0x400000, 7.6f); + Bow2(id + 0x500000, 9.7f); + Enrage(id + 0x600000, 15.8f); } private void ForkBowStaffSword(uint id) { - Bow1(id, 6.3f); - Staff1(id + 0x100000, 7.9f); + Bow1(id, 6); // note: very high variance here... + Staff1(id + 0x100000, 7.9f); // note: very high variance here... Sword1(id + 0x200000, 7.5f); - Bow2(id + 0x300000, 8.4f); - Staff2(id + 0x400000, 7.6f); - Sword2(id + 0x500000, 7.5f); // TODO: timing - SimpleState(id + 0xFF0000, 10, "Enrage"); // TODO: timing, action + Bow2(id + 0x300000, 8.5f); + Staff2(id + 0x400000, 8); // note: very high variance here... + Sword2(id + 0x500000, 7.5f); + Enrage(id + 0x600000, 20.5f); } private void Sword1(uint id, float delay) { AllegiantArsenalAOE(id, delay); - Cast(id + 0x10000, AID.HotAndColdSword, 4.9f, 3); + Cast(id + 0x10000, AID.HotAndColdSword, 4.5f, 3); // note: large variance // +1.1s: temperature statuses - Cast(id + 0x10010, AID.UnwaveringApparition, 4.1f, 3); - Targetable(id + 0x10020, false, 5.7f, "Disappear"); + Cast(id + 0x10010, AID.UnwaveringApparition, 6, 3); + Targetable(id + 0x10020, false, 5.7f, "Disappear"); // note: large variance BladeOfEntropy(id + 0x10030, 0.1f, "Sword 1"); - BladeOfEntropy(id + 0x10040, 3.7f, "Sword 2"); + BladeOfEntropy(id + 0x10040, 4.0f, "Sword 2"); // note: large variance Targetable(id + 0x10050, true, 3.1f, "Reappear"); GloryOfBozja(id + 0x20000, 6.5f); @@ -113,10 +112,10 @@ private void Bow1(uint id, float delay) { AllegiantArsenalAOE(id, delay); - Cast(id + 0x10000, AID.QuickMarchBow, 3.2f, 3) + Cast(id + 0x10000, AID.QuickMarchBow, 3.1f, 3) .ActivateOnEnter() .ActivateOnEnter(); // debuffs are applied ~1s after cast end - WrathOfBozja(id + 0x10010, 3.1f); + WrathOfBozja(id + 0x10010, 3.1f, true); Cast(id + 0x10020, AID.FlamesOfBozja, 3.2f, 3); // +1.1s: flames of bozja aoe cast start ComponentCondition(id + 0x10030, 5.7f, comp => comp.NumActiveForcedMarches > 0, "Forced march start"); @@ -132,7 +131,7 @@ private void Bow1(uint id, float delay) ComponentCondition(id + 0x20030, 2.2f, comp => comp.AOE == null, "Bow 1 resolve") .DeactivateOnExit(); - GloryOfBozja(id + 0x30000, 5.3f); + GloryOfBozja(id + 0x30000, 5.3f); // TODO: this seems to have slightly different timings depending on forks... } private void Staff1(uint id, float delay) @@ -155,26 +154,26 @@ private void Staff1(uint id, float delay) // +0.3s: actual aoes (who cares) // +2.0s: blast cast starts - ComponentCondition(id + 0x20000, 6.7f, comp => comp.NumActiveForcedMarches > 0, "Forced march start") + ComponentCondition(id + 0x20000, 6.8f, comp => comp.NumActiveForcedMarches > 0, "Forced march start") .ActivateOnEnter() .ActivateOnEnter(); ComponentCondition(id + 0x20010, 3.3f, comp => comp.NumCasts > 0, "Orbs hit") .DeactivateOnExit() .DeactivateOnExit(); - GloryOfBozja(id + 0x30000, 7.9f); + GloryOfBozja(id + 0x30000, 7.9f); // TODO: this seems to have slightly different timings depending on forks... } private void Sword2(uint id, float delay) { AllegiantArsenalAOE(id, delay); - Cast(id + 0x10000, AID.HotAndColdSword, 4.4f, 3); + Cast(id + 0x10000, AID.HotAndColdSword, 4.4f, 3); // note: large variance Cast(id + 0x10010, AID.ElementalBrandSword, 4.1f, 3); Cast(id + 0x10020, AID.UnwaveringApparition, 3.2f, 3); Targetable(id + 0x10030, false, 6.0f, "Disappear"); BladeOfEntropy(id + 0x10040, 0.1f, "Sword 1"); - BladeOfEntropy(id + 0x10050, 3.9f, "Sword 2"); + BladeOfEntropy(id + 0x10050, 4.0f, "Sword 2"); Targetable(id + 0x10060, true, 3.1f, "Reappear"); GloryOfBozja(id + 0x20000, 6.5f); @@ -184,7 +183,7 @@ private void Bow2(uint id, float delay) { AllegiantArsenalAOE(id, delay); - Cast(id + 0x10000, AID.UnseenEye, 3.1f, 3); + Cast(id + 0x10000, AID.UnseenEyeBow, 3.1f, 3); Cast(id + 0x10010, AID.FlamesOfBozja, 3.1f, 3) .ActivateOnEnter(); // PATE events happen together with cast-start, actual casts start ~2.1s later - if we want to rely on former, need to activate earlier ComponentCondition(id + 0x10020, 5.1f, comp => comp.NumCasts > 0, "Criss-cross") @@ -192,7 +191,7 @@ private void Bow2(uint id, float delay) ComponentCondition(id + 0x10030, 5, comp => comp.NumCasts > 0, "Single safe row") .ActivateOnEnter(); // activate late, since criss-cross have to be resolved first - Cast(id + 0x20000, AID.HotAndColdBow, 0, 3); // this can start slightly earlier than flames of bozja end, but whatever... + Cast(id + 0x20000, AID.HotAndColdBow, 0, 3); // note: very high variance, sometimes even starts slightly beofre flames of bozja end... Cast(id + 0x20010, AID.ElementalBrandBow, 4.2f, 3); Cast(id + 0x20020, AID.QuickMarchBow, 3.2f, 3); Cast(id + 0x20030, AID.ShimmeringShot, 3.9f, 3); @@ -202,11 +201,11 @@ private void Bow2(uint id, float delay) ComponentCondition(id + 0x20050, 4, comp => comp.NumCasts > 0, "Arrows hit") .DeactivateOnExit() .DeactivateOnExit(); - ComponentCondition(id + 0x20060, 2.1f, comp => comp.AOE == null, "Bow 1 resolve") + ComponentCondition(id + 0x20060, 2.2f, comp => comp.AOE == null, "Bow 2 resolve") .DeactivateOnExit(); GloryOfBozja(id + 0x30000, 5.3f); - WrathOfBozja(id + 0x40000, 3.2f); + WrathOfBozja(id + 0x40000, 3.2f, false); } private void Staff2(uint id, float delay) @@ -215,12 +214,11 @@ private void Staff2(uint id, float delay) Cast(id + 0x10000, AID.HotAndColdStaff, 3.1f, 3); Cast(id + 0x10010, AID.ElementalBrandStaff, 4.1f, 3); - // TODO: timings below are guesses until i get a log Cast(id + 0x10020, AID.FreedomOfBozja, 3.2f, 3) .ActivateOnEnter() .ActivateOnEnter(); - Cast(id + 0x10030, AID.UnseenEye, 3.1f, 3); - ComponentCondition(id + 0x10040, 1.2f, comp => comp.NumCasts > 0, "Proximity", 10) + Cast(id + 0x10030, AID.UnseenEyeStaff, 3.1f, 3); + ComponentCondition(id + 0x10040, 1.0f, comp => comp.NumCasts > 0, "Proximity", 10) .DeactivateOnExit() .DeactivateOnExit(); @@ -230,13 +228,16 @@ private void Staff2(uint id, float delay) .DeactivateOnExit() .DeactivateOnExit(); - GloryOfBozja(id + 0x30000, 7.9f); + GloryOfBozja(id + 0x30000, 8); } - // TODO: component - private void WrathOfBozja(uint id, float delay) + private void WrathOfBozja(uint id, float delay, bool bow) { - CastMulti(id, new[] { AID.WrathOfBozja, AID.WrathOfBozjaBow }, delay, 5, "Tankbuster") + Cast(id, bow ? AID.WrathOfBozjaBow : AID.WrathOfBozja, delay, 5, "Tankbuster") + .ActivateOnEnter(!bow) + .ActivateOnEnter(bow) + .DeactivateOnExit(!bow) + .DeactivateOnExit(bow) .SetHint(StateMachine.StateHint.Tankbuster); } @@ -259,4 +260,9 @@ private void BladeOfEntropy(uint id, float delay, string name) .ActivateOnEnter() .DeactivateOnExit(); } + + private void Enrage(uint id, float delay) + { + Cast(id, AID.Enrage, delay, 12, "Enrage"); // boss becomes untargetable at the end of the cast + } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/Staff.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/Staff.cs similarity index 97% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/Staff.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/Staff.cs index 3cc1315dbd..d9d7c3bd97 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/Staff.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/Staff.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; class FreedomOfBozja : TemperatureAOE { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/TemperatureAOE.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/TemperatureAOE.cs similarity index 97% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/TemperatureAOE.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/TemperatureAOE.cs index 346d7c1445..c648523d93 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS5TrinityAvowed/TemperatureAOE.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6TrinityAvowed/TemperatureAOE.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS5TrinityAvowed; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6TrinityAvowed; abstract class TemperatureAOE : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/AddPhaseArena.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/AddPhaseArena.cs similarity index 97% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/AddPhaseArena.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/AddPhaseArena.cs index f285867316..5c02857a3e 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/AddPhaseArena.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/AddPhaseArena.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6StygimolochLord; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; class AddPhaseArena : BossComponent { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/CrushingHoof.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/CrushingHoof.cs similarity index 92% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/CrushingHoof.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/CrushingHoof.cs index dbea90351b..b7d9d87dfc 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/CrushingHoof.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/CrushingHoof.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6StygimolochLord; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; class CrushingHoof : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7.cs similarity index 83% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7.cs index f29ded7f2a..13e0b5bde2 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7.cs @@ -1,4 +1,9 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6StygimolochLord; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; + +class FoeSplitter : Components.Cleave +{ + public FoeSplitter() : base(ActionID.MakeSpell(AID.FoeSplitter), new AOEShapeCone(9, 45.Degrees())) { } // TODO: verify angle +} class ThunderousDischarge : Components.CastCounter { @@ -32,13 +37,13 @@ public Electrocution() : base(ActionID.MakeSpell(AID.Electrocution), 3) { } // TODO: ManaFlame component - show reflect hints [ModuleInfo(GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9759)] -public class DRS6 : BossModule +public class DRS7 : BossModule { private IReadOnlyList _monks; private IReadOnlyList _ballsEarth; private IReadOnlyList _ballsFire; - public DRS6(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-416, -184), 35)) + public DRS7(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-416, -184), 35)) { _monks = Enemies(OID.StygimolochMonk); _ballsEarth = Enemies(OID.BallOfEarth); diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7Enums.cs similarity index 94% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6Enums.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7Enums.cs index aeab7ae762..f2f5d090d7 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7Enums.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6StygimolochLord; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; public enum OID : uint { @@ -15,6 +15,7 @@ public enum AID : uint AutoAttack = 6497, // Boss->player, no cast, single-target Teleport = 22490, // Boss->location, no cast, single-target, teleport FoeSplitter = 22487, // Boss->player, 5.0s cast, range 9 ?-degree cone tankbuster + ViciousSwipe = 21842, // Boss->self, no cast, range 8 circle, knockback 15 ThunderousDischarge = 22482, // Boss->self, 5.0s cast, single-target, visual (raidwide) ThunderousDischargeExtra = 22483, // Helper->self, no cast, single-target, visual? ThunderousDischargeAOE = 23352, // Helper->self, no cast, range 70 circle raidwide @@ -52,5 +53,6 @@ public enum SID : uint public enum IconID : uint { + FoeSplitter = 198, // player RapidBolts = 160, // player }; diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7States.cs similarity index 66% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6States.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7States.cs index 55d6b1487c..7912ce6d7a 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/DRS6States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/DRS7States.cs @@ -1,10 +1,11 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6StygimolochLord; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; -class DRS6States : StateMachineBuilder +class DRS7States : StateMachineBuilder { - public DRS6States(BossModule module) : base(module) + public DRS7States(BossModule module) : base(module) { SimplePhase(0, PhaseBeforeAdds, "Before adds") + .ActivateOnEnter() .Raw.Update = () => Module.PrimaryActor.IsDestroyed || !Module.PrimaryActor.IsTargetable; SimplePhase(1, PhaseAdds, "Adds") .ActivateOnEnter() @@ -16,18 +17,22 @@ public DRS6States(BossModule module) : base(module) void PhaseBeforeAdds(uint id) { FoeSplitter(id, 8.2f); - // TODO: vicious swipe > whack > swing > rapid bolts x2 > ??? + ViciousSwipe(id + 0x10000, 8.2f); + Whack(id + 0x20000, 2.4f); + ThousandTonzeSwing(id + 0x30000, 4.7f); + // TODO: rapid bolts x2 > repeat? SimpleState(id + 0xFF0000, 100, "???"); } void PhaseAdds(uint id) { - MemoryOfTheLabyrinth(id, 1.3f); - LabyrinthineFateFatefulWords(id + 0x10000, 24.3f); + MemoryOfTheLabyrinth(id, 2); // note: large variance + LabyrinthineFateFatefulWords(id + 0x10000, 24.4f); DevastatingBolt(id + 0x20000, 2.6f); RendingBolt(id + 0x30000, 2.8f); LabyrinthineFateDevastatingBoltRendingBoltFatefulWords(id + 0x40000, 7.3f); - // TODO: rending > devastating > loop + RendingBoltDevastatingBolt(id + 0x50000, 4.6f); + // TODO: repeat? SimpleState(id + 0xFF0000, 100, "???"); } @@ -40,18 +45,28 @@ void PhaseAfterAdds(uint id) RapidBolts(id + 0x40000, 2.1f); CrushingHoof(id + 0x50000, 2.1f); Whack(id + 0x60000, 2.3f); - FoeSplitter(id + 0x70000, 12.0f); - // TODO: vicious swipe > whack > loop + FoeSplitter(id + 0x70000, 11.8f); + ViciousSwipe(id + 0x80000, 5.2f); + Whack(id + 0x90000, 2.3f); + // TODO: repeat? SimpleState(id + 0xFF0000, 100, "???"); } - // TODO: component private void FoeSplitter(uint id, float delay) { Cast(id, AID.FoeSplitter, delay, 5, "Tankbuster") + .ActivateOnEnter() + .DeactivateOnExit() .SetHint(StateMachine.StateHint.Tankbuster); } + private void ViciousSwipe(uint id, float delay) + { + ComponentCondition(id, delay, comp => comp.NumCasts > 0, "Knockback") + .ActivateOnEnter() + .DeactivateOnExit(); + } + private void ThunderousDischarge(uint id, float delay) { Cast(id, AID.ThunderousDischarge, delay, 5); @@ -95,14 +110,14 @@ private void Whack(uint id, float delay) private void MemoryOfTheLabyrinth(uint id, float delay) { Cast(id, AID.MemoryOfTheLabyrinth, delay, 3); - Condition(id + 0x10, 1, () => Module.Enemies(OID.StygimolochMonk).Any(a => a.IsTargetable), "Adds appear"); + Condition(id + 0x10, 0.9f, () => Module.Enemies(OID.StygimolochMonk).Any(a => a.IsTargetable), "Adds appear"); } - private void FatefulWords(uint id, float delay) + private State FatefulWords(uint id, float delay) { Cast(id, AID.FatefulWords, delay, 5) .ActivateOnEnter(); - ComponentCondition(id + 2, 0.5f, comp => comp.NumCasts > 0, "Knockback/attract") + return ComponentCondition(id + 2, 0.5f, comp => comp.NumCasts > 0, "Knockback/attract") .DeactivateOnExit(); } @@ -112,10 +127,10 @@ private void LabyrinthineFateFatefulWords(uint id, float delay) FatefulWords(id + 0x10, 3.3f); } - private void DevastatingBolt(uint id, float delay) + private State DevastatingBolt(uint id, float delay) { Cast(id, AID.DevastatingBolt, delay, 3); - ComponentCondition(id + 0x10, 4.5f, comp => comp.NumCasts > 0, "Alcoves") + return ComponentCondition(id + 0x10, 4.5f, comp => comp.NumCasts > 0, "Alcoves") .ActivateOnEnter() .ActivateOnEnter() .DeactivateOnExit() @@ -134,8 +149,18 @@ private void LabyrinthineFateDevastatingBoltRendingBoltFatefulWords(uint id, flo { Cast(id, AID.LabyrinthineFate, delay, 3); DevastatingBolt(id + 0x100, 3.3f); - // TODO: timings... - RendingBolt(id + 0x200, 5); - FatefulWords(id + 0x300, 5); + + Cast(id + 0x200, AID.RendingBolt, 2.8f, 3, "Puddles first") + .ActivateOnEnter(); + FatefulWords(id + 0x210, 3.3f) + .DeactivateOnExit(); // last puddles resolve ~0.7s into cast + } + + private void RendingBoltDevastatingBolt(uint id, float delay) + { + Cast(id, AID.RendingBolt, delay, 3, "Puddles first") + .ActivateOnEnter(); + DevastatingBolt(id + 0x100, 1.3f) + .DeactivateOnExit(); // last puddles resolve ~0.3s before cast end } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/FatefulWords.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/FatefulWords.cs similarity index 94% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/FatefulWords.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/FatefulWords.cs index cdf371cd8e..afa34ea8ee 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/FatefulWords.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/FatefulWords.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6StygimolochLord; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; class FatefulWords : Components.Knockback { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/RapidBolts.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/RapidBolts.cs similarity index 95% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/RapidBolts.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/RapidBolts.cs index 28ff073859..1492e9f806 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS6StygimolochLord/RapidBolts.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/RapidBolts.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS6StygimolochLord; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; // TODO: generalize to 'baited puddles' component class RapidBoltsBait : Components.UniformStackSpread diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/ViciousSwipe.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/ViciousSwipe.cs new file mode 100644 index 0000000000..db04ea9612 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7StygimolochLord/ViciousSwipe.cs @@ -0,0 +1,20 @@ + +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7StygimolochLord; + +class ViciousSwipe : Components.Knockback +{ + private Source? _source; + + private static readonly AOEShapeCircle _shape = new(8); + + public ViciousSwipe() : base(ActionID.MakeSpell(AID.ViciousSwipe)) { } + + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_source); + + public override void Init(BossModule module) => _source = new(module.PrimaryActor.Position, 15, module.WorldState.CurrentTime.AddSeconds(module.StateMachine.ActiveState?.Duration ?? 0), _shape); + + public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + { + arena.AddCircle(module.PrimaryActor.Position, _shape.Radius, ArenaColor.Danger); + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/AboveBoard.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/AboveBoard.cs new file mode 100644 index 0000000000..8188ccaef4 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/AboveBoard.cs @@ -0,0 +1,85 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; + +// note: this is exactly the same as queen's guard component +class AboveBoard : Components.GenericAOEs +{ + public enum State { Initial, ThrowUpDone, ShortExplosionsDone, LongExplosionsDone } + + public State CurState { get; private set; } + private IReadOnlyList _smallBombs = ActorEnumeration.EmptyList; + private IReadOnlyList _bigBombs = ActorEnumeration.EmptyList; + private bool _invertedBombs; // bombs are always either all normal (big=short) or all inverted + private BitMask _invertedPlayers; // default for player is 'long', short is considered inverted (has visible status) + private DateTime _activation; + + private static readonly AOEShapeCircle _shape = new(10); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + var imminentBombs = AreBigBombsDangerous(slot) ? _bigBombs : _smallBombs; + return imminentBombs.Select(b => new AOEInstance(_shape, b.Position, new(), _activation)); + } + + public override void Init(BossModule module) + { + _smallBombs = module.Enemies(OID.AetherialBolt); + _bigBombs = module.Enemies(OID.AetherialBurst); + _activation = module.WorldState.CurrentTime.AddSeconds(14.4f); + } + + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + switch ((SID)status.ID) + { + case SID.ReversalOfForces: + if ((OID)actor.OID is OID.AetherialBolt or OID.AetherialBurst) + _invertedBombs = true; + else + _invertedPlayers.Set(module.Raid.FindSlot(actor.InstanceID)); + break; + case SID.AboveBoardPlayerLong: + case SID.AboveBoardPlayerShort: + case SID.AboveBoardBombLong: + case SID.AboveBoardBombShort: + AdvanceState(State.ThrowUpDone); + break; + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + switch ((AID)spell.Action.ID) + { + case AID.LotsCastBigShort: + //case AID.LotsCastSmallShort: + AdvanceState(State.ShortExplosionsDone); + break; + case AID.LotsCastLong: + AdvanceState(State.LongExplosionsDone); + _activation = module.WorldState.CurrentTime.AddSeconds(4.2f); + break; + } + } + + private bool AreBigBombsDangerous(int slot) + { + if (_invertedPlayers[slot]) + { + // inverted players fall right before first bomb explosion, so they have to avoid first bombs, then move to avoid second bombs + var firstSetImminent = CurState < State.ShortExplosionsDone; + return firstSetImminent != _invertedBombs; // first set is big if inverted + } + else + { + // normally players fall right before second bomb explosion, so they only avoid second bombs + // second bombs are normally small, big if inverted + return _invertedBombs; + } + } + + private void AdvanceState(State dest) + { + if (CurState < dest) + CurState = dest; + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/Chess.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/Chess.cs similarity index 99% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/Chess.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/Chess.cs index a57967181d..38f3a2833d 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/Chess.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/Chess.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; abstract class Chess : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8.cs similarity index 86% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8.cs index 8a9577cb3e..265fb0f74f 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; class NorthswainsGlow : Components.SelfTargetedAOEs { @@ -43,6 +43,11 @@ class PawnOff : Components.SelfTargetedAOEs public PawnOff() : base(ActionID.MakeSpell(AID.PawnOffReal), new AOEShapeCircle(20)) { } } +class OptimalPlaySword : Components.SelfTargetedAOEs +{ + public OptimalPlaySword() : base(ActionID.MakeSpell(AID.OptimalPlaySword), new AOEShapeCircle(10)) { } +} + class OptimalPlayShield : Components.SelfTargetedAOEs { public OptimalPlayShield() : base(ActionID.MakeSpell(AID.OptimalPlayShield), new AOEShapeDonut(5, 60)) { } @@ -54,7 +59,7 @@ class OptimalPlayCone : Components.SelfTargetedAOEs } [ModuleInfo(GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9863)] -public class DRS7 : BossModule +public class DRS8 : BossModule { - public DRS7(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-272, -415), 25)) { } // note: initially arena is square, but it quickly changes to circle + public DRS8(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-272, -415), 25)) { } // note: initially arena is square, but it quickly changes to circle } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8Enums.cs similarity index 76% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7Enums.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8Enums.cs index a6fb8c2da4..602d71b428 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8Enums.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; public enum OID : uint { @@ -12,6 +12,8 @@ public enum OID : uint BallLightning = 0x2FB0, // R2.000, spawn during fight AutomaticTurret = 0x311F, // R3.000, spawn during fight AetherialSphere = 0x3117, // R2.500, spawn during fight + AetherialBolt = 0x3119, // R0.600, spawn during fight (small bomb) + AetherialBurst = 0x311A, // R1.200, spawn during fight (big bomb) ProtectiveDome = 0x1EB12C, // R0.500, EventObj type, spawn during fight }; @@ -60,6 +62,7 @@ public enum AID : uint QueensShotAOE = 23086, // Helper->self, 7.0s cast, range 60 circle, raidwide that requires directing unseen side to caster TurretsTourUnseen = 23087, // AutomaticTurret->self, 3.0s cast, range 50 width 5 rect aoe that requires directing unseen side to caster + SwordOmen = 23037, // QueensKnight->self, 3.0s cast, single-target, apply sword-bearer status ShieldOmen = 23038, // QueensKnight->self, 3.0s cast, single-target, apply shield-bearer status DoubleGambit = 23067, // QueensSoldier->self, 5.0s cast, single-target, visual (summon 4 pawns) OptimalOffensive = 23043, // QueensKnight->location, 7.0s cast, width 5 rect charge @@ -76,13 +79,34 @@ public enum AID : uint PawnOffFake = 23070, // SoldierAvatar->self, 7.0s cast, range 20 circle fake aoe AutomaticTurretRP3 = 23078, // QueensGunner->self, 3.0s cast, single-target, visual (spawn turrets) - // OptimalPlaySword = 23039? + OptimalPlaySword = 23039, // QueensKnight->self, 5.0s cast, range 10 circle OptimalPlayShield = 23040, // QueensKnight->self, 5.0s cast, range 5-60 donut OptimalPlayCone = 23041, // Helper->self, 5.0s cast, range 60 270-degree cone TurretsTour = 23079, // QueensGunner->self, 5.0s cast, single-target, visual (turret aoes) TurretsTourAOE1 = 23080, // Helper->location, 5.0s cast, width 6 rect charge aoe TurretsTourAOE2 = 23082, // AutomaticTurret->self, no cast, range 50 width 6 rect TurretsTourAOE3 = 23081, // AutomaticTurret->location, no cast, width 6 rect charge + + Bombslinger = 23359, // QueensWarrior->self, 3.0s cast, single-target, visual (spawn bombs) + HeavensWrath = 23030, // Boss->self, 3.0s cast, single-target, visual (preparation for knockback) + HeavensWrathKnockback = 23031, // Helper->self, 5.0s cast, range 60 width 100 rect, knockback 15 + HeavensWrathVisual = 23032, // Helper->self, 5.0s cast, single-target, visual (knockback) + FieryPortent = 23073, // QueensSoldier->self, 6.0s cast, range 60 circle, visual (pyretic buff application) + IcyPortent = 23074, // QueensSoldier->self, 6.0s cast, range 60 circle, visual (move to avoid effect) + AboveBoard = 23051, // QueensWarrior->self, 6.0s cast, range 60 circle, visual (throw up) + AboveBoardExtra = 23438, // Helper->self, 6.0s cast, range 60 circle, visual (???) + LotsCastBigShort = 23433, // AetherialBurst->location, no cast, range 10 circle + LotsCastSmallLong = 23432, // AetherialBolt->location, no cast, range 10 circle visual + LotsCastLong = 23478, // Helper->location, 1.2s cast, range 10 circle + + SoftEnrageW = 23062, // QueensWarrior->self, 5.0s cast, range 60 circle, visual (raidwide) + SoftEnrageWAOE = 23412, // Helper->self, 5.0s cast, range 60 circle, raidwide + SoftEnrageK = 23048, // QueensKnight->self, 5.0s cast, range 60 circle, visual (raidwide) + SoftEnrageKAOE = 23411, // Helper->self, 5.0s cast, range 60 circle, raidwide + SoftEnrageG = 23093, // QueensGunner->self, 5.0s cast, range 60 circle, visual (raidwide) + SoftEnrageGAOE = 23414, // Helper->self, 5.0s cast, range 60 circle, raidwide + SoftEnrageS = 23075, // QueensSoldier->self, 5.0s cast, range 60 circle, visual (raidwide) + SoftEnrageSAOE = 23413, // Helper->self, 5.0s cast, range 60 circle, raidwide }; public enum SID : uint @@ -106,4 +130,8 @@ public enum SID : uint LeftUnseen = 1708, // none->player, extra=0xEA BackUnseen = 1709, // none->player, extra=0xE8 ShieldBearer = 2446, // QueensKnight->QueensKnight, extra=0x0 + AboveBoardPlayerLong = 2426, // none->player, extra=0x3E8 + AboveBoardPlayerShort = 2427, // none->player, extra=0x3E8 + AboveBoardBombLong = 2428, // none->AetherialBurst/AetherialBolt, extra=0x3E8 + AboveBoardBombShort = 2429, // none->AetherialBolt/AetherialBurst, extra=0x3E8 }; diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8States.cs similarity index 59% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7States.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8States.cs index 5ce31d9c6d..00be68ca0a 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/DRS7States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/DRS8States.cs @@ -1,8 +1,8 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; -class DRS7States : StateMachineBuilder +class DRS8States : StateMachineBuilder { - public DRS7States(BossModule module) : base(module) + public DRS8States(BossModule module) : base(module) { SimplePhase(0, Phase1, "P1") .Raw.Update = () => Module.PrimaryActor.IsDestroyed || Module.PrimaryActor.HP.Cur <= 1 || (Module.PrimaryActor.CastInfo?.IsSpell(AID.GodsSaveTheQueen) ?? false); @@ -14,9 +14,9 @@ private void Phase1(uint id) EmpyreanIniquity(id, 10.2f); QueensWill(id + 0x10000, 13.7f); CleansingSlash(id + 0x20000, 10.9f); - EmpyreanIniquity(id + 0x30000, 4.1f); + EmpyreanIniquity(id + 0x30000, 4.2f); QueensEdict(id + 0x40000, 11.5f); - SimpleState(id + 0x50000, 15.5f, "Second phase"); + SimpleState(id + 0x50000, 12.5f, "Second phase"); } private void Phase2(uint id) @@ -24,15 +24,18 @@ private void Phase2(uint id) GodsSaveTheQueen(id, 0); MaelstromsBolt(id + 0x10000, 31.7f); RelentlessPlay1(id + 0x20000, 7.3f); - CleansingSlash(id + 0x30000, 4.5f); + CleansingSlash(id + 0x30000, 4.4f); RelentlessPlay2(id + 0x40000, 8.2f); EmpyreanIniquity(id + 0x50000, 5.1f); QueensEdict(id + 0x60000, 11.5f); CleansingSlash(id + 0x70000, 2.1f); RelentlessPlay3(id + 0x80000, 10.2f); - MaelstromsBolt(id + 0x90000, 16.3f); // not sure about delay... - // TODO: empyrean iniquity > relentless play 4 > soft enrage - SimpleState(id + 0xFF0000, 100, "???"); + MaelstromsBolt(id + 0x90000, 16.3f); + EmpyreanIniquity(id + 0xA0000, 6.2f); + RelentlessPlay4(id + 0xB0000, 8.2f); + RelentlessPlay5(id + 0xC0000, 0.1f); + // TODO: boss gains damage up at +6.6, then presumably would start some enrage cast... + SimpleState(id + 0xD0000, 15, "Enrage"); } private void EmpyreanIniquity(uint id, float delay) @@ -57,7 +60,7 @@ private void QueensWill(uint id, float delay) Cast(id, AID.QueensWill, delay, 5) .ActivateOnEnter() // statuses appear ~0.7s after cast end .OnEnter(() => Module.Arena.Bounds = new ArenaBoundsSquare(Module.Bounds.Center, Module.Bounds.HalfSize)); // deathwall changes around cast start - Cast(id + 0x10, AID.NorthswainsGlow, 3.1f, 3) + Cast(id + 0x10, AID.NorthswainsGlow, 3.2f, 3) .ActivateOnEnter(); // aoe casts start ~0.8s after visual cast end Cast(id + 0x20, AID.BeckAndCallToArmsWillKW, 3.1f, 5); ComponentCondition(id + 0x30, 2.6f, comp => comp.NumCasts > 0) @@ -86,7 +89,7 @@ private void QueensEdict(uint id, float delay) ComponentCondition(id + 0x40, 2.4f, comp => comp.NumStuns > 0, "Super chess columns"); ComponentCondition(id + 0x41, 1.9f, comp => comp.NumCasts >= 4); - CastStart(id + 0x50, AID.GunnhildrsBlades, 2.9f); + CastStart(id + 0x50, AID.GunnhildrsBlades, 2.8f); ComponentCondition(id + 0x51, 1.3f, comp => comp.NumStuns == 0); // 2nd edict movement starts ~1.0s before this ComponentCondition(id + 0x52, 9, comp => comp.NumStuns > 0, "Super chess safespot"); CastEnd(id + 0x53, 3.7f) @@ -117,7 +120,7 @@ private void RelentlessPlay1(uint id, float delay) Cast(id, AID.RelentlessPlay, delay, 5); // +3.0s: tethers/icons - ActorCast(id + 0x10, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.ReversalOfForces, 3.2f, 4); // gunner casts automatic turret - starts 1s later, ends at the same time + ActorCast(id + 0x10, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.ReversalOfForces, 3.1f, 4); // gunner casts automatic turret - starts 1s later, ends at the same time // +1.0s: tethers are replaced with statuses CastStart(id + 0x20, AID.NorthswainsGlow, 1.1f); @@ -128,14 +131,14 @@ private void RelentlessPlay1(uint id, float delay) .ActivateOnEnter(); // aoes start ~0.8s after visual cast end // +1.0s: unseen statuses - ActorCastStart(id + 0x30, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.WindsOfWeight, 3.1f); - ActorCastStart(id + 0x31, Module.Enemies(OID.QueensGunner).FirstOrDefault, AID.QueensShot, 0.1f) + ActorCastStart(id + 0x30, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.WindsOfWeight, 3.0f); + ActorCastStart(id + 0x31, Module.Enemies(OID.QueensGunner).FirstOrDefault, AID.QueensShot, 0.2f) .ActivateOnEnter(); - ActorCastEnd(id + 0x32, Module.Enemies(OID.QueensWarrior).FirstOrDefault, 5.9f, false, "Wind/gravity") + ActorCastEnd(id + 0x32, Module.Enemies(OID.QueensWarrior).FirstOrDefault, 5.8f, false, "Wind/gravity") .ActivateOnEnter() .DeactivateOnExit() .DeactivateOnExit(); - ActorCastEnd(id + 0x33, Module.Enemies(OID.QueensGunner).FirstOrDefault, 1.1f, false, "Face gunner") + ActorCastEnd(id + 0x33, Module.Enemies(OID.QueensGunner).FirstOrDefault, 1.2f, false, "Face gunner") .DeactivateOnExit(); // +0.5s: turrets start their casts @@ -150,16 +153,16 @@ private void RelentlessPlay2(uint id, float delay) ActorCast(id + 0x10, Module.Enemies(OID.QueensKnight).FirstOrDefault, AID.ShieldOmen, 3.2f, 3); ActorCastStart(id + 0x20, Module.Enemies(OID.QueensSoldier).FirstOrDefault, AID.DoubleGambit, 4.5f); - Targetable(id + 0x21, false, 0.5f, "Disappear"); + Targetable(id + 0x21, false, 0.4f, "Disappear"); ActorCastStart(id + 0x22, Module.Enemies(OID.QueensKnight).FirstOrDefault, AID.OptimalOffensive, 1.4f); CastStartMulti(id + 0x23, new[] { AID.JudgmentBladeR, AID.JudgmentBladeL }, 1.9f) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter(); - ActorCastEnd(id + 0x24, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 1.2f) + ActorCastEnd(id + 0x24, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 1.3f) .ActivateOnEnter(); ActorCastStart(id + 0x25, Module.Enemies(OID.QueensSoldier).FirstOrDefault, AID.SecretsRevealed, 3.2f); // right before cast start, 2 unsafe avatars are tethered to caster - ActorCastEnd(id + 0x26, Module.Enemies(OID.QueensKnight).FirstOrDefault, 0.7f, false, "Charge + Knockback") + ActorCastEnd(id + 0x26, Module.Enemies(OID.QueensKnight).FirstOrDefault, 0.6f, false, "Charge + Knockback") .DeactivateOnExit() .DeactivateOnExit(); CastEnd(id + 0x27, 1.9f); @@ -167,9 +170,9 @@ private void RelentlessPlay2(uint id, float delay) .DeactivateOnExit(); ComponentCondition(id + 0x29, 0.5f, comp => comp.NumCasts > 0, "Sphere explosion") .DeactivateOnExit(); - ActorCastEnd(id + 0x2A, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 1.6f); + ActorCastEnd(id + 0x2A, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 1.7f); - CastMulti(id + 0x30, new[] { AID.JudgmentBladeR, AID.JudgmentBladeL }, 2.1f, 7) + CastMulti(id + 0x30, new[] { AID.JudgmentBladeR, AID.JudgmentBladeL }, 2.2f, 7) .ActivateOnEnter() .ActivateOnEnter(); // cast starts ~2.7s after judgment blade; we could show hints much earlier based on tethers ComponentCondition(id + 0x32, 0.3f, comp => comp.NumCasts > 0, "Cleave") @@ -177,24 +180,95 @@ private void RelentlessPlay2(uint id, float delay) ComponentCondition(id + 0x33, 2.4f, comp => comp.NumCasts > 0, "Real/fake aoes") .DeactivateOnExit(); - Targetable(id + 0x40, true, 2.1f, "Reappear"); + Targetable(id + 0x40, true, 2.0f, "Reappear"); } private void RelentlessPlay3(uint id, float delay) { Cast(id, AID.RelentlessPlay, delay, 5); - ActorCastMulti(id + 0x10, Module.Enemies(OID.QueensKnight).FirstOrDefault, new[] { AID.ShieldOmen }, 3.2f, 3); // TODO: could also be sword omen + ActorCastMulti(id + 0x10, Module.Enemies(OID.QueensKnight).FirstOrDefault, new[] { AID.SwordOmen, AID.ShieldOmen }, 3.1f, 3); // note: gunner starts automatic turret visual together with optimal play - ActorCastMulti(id + 0x20, Module.Enemies(OID.QueensKnight).FirstOrDefault, new[] { AID.OptimalPlayShield }, 6.4f, 5, false, "Cone + circle/donut") + ActorCastMulti(id + 0x20, Module.Enemies(OID.QueensKnight).FirstOrDefault, new[] { AID.OptimalPlaySword, AID.OptimalPlayShield }, 6.5f, 5, false, "Cone + circle/donut") + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() + .DeactivateOnExit() .DeactivateOnExit() .DeactivateOnExit(); - ActorCast(id + 0x30, Module.Enemies(OID.QueensGunner).FirstOrDefault, AID.TurretsTour, 1.1f, 5, false, "Turrets start") + ActorCast(id + 0x30, Module.Enemies(OID.QueensGunner).FirstOrDefault, AID.TurretsTour, 1, 5, false, "Turrets start") .ActivateOnEnter(); - ComponentCondition(id + 0x40, 1.8f, comp => comp.NumCasts >= 4, "Turrets resolve") + ComponentCondition(id + 0x40, 1.7f, comp => comp.NumCasts >= 4, "Turrets resolve") .DeactivateOnExit(); } + + private void RelentlessPlay4(uint id, float delay) + { + Cast(id, AID.RelentlessPlay, delay, 5); + ActorCast(id + 0x10, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.Bombslinger, 3.1f, 3); + // +0.9s: bombs spawn + + ActorCastStart(id + 0x20, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.ReversalOfForces, 3.2f); // icons/tethers appear ~0.1s before cast start + CastStart(id + 0x21, AID.HeavensWrath, 2.9f); + ActorCastEnd(id + 0x22, Module.Enemies(OID.QueensWarrior).FirstOrDefault, 1.1f); + CastEnd(id + 0x23, 1.9f); + + ComponentCondition(id + 0x30, 0.8f, comp => comp.Casters.Count > 0) + .ActivateOnEnter(); + ActorCastStartMulti(id + 0x31, Module.Enemies(OID.QueensSoldier).FirstOrDefault, new[] { AID.FieryPortent, AID.IcyPortent }, 2.2f) + .ActivateOnEnter() + .ActivateOnEnter(); + ComponentCondition(id + 0x32, 2.8f, comp => comp.NumCasts > 0, "Knockback") + .ActivateOnEnter() + .DeactivateOnExit() + .DeactivateOnExit(); + ActorCastStart(id + 0x33, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.AboveBoard, 0.5f); + Targetable(id + 0x34, false, 1.8f, "Boss disappears"); + ActorCastEnd(id + 0x35, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 0.9f, false, "Move/stay") + .DeactivateOnExit(); + ActorCastEnd(id + 0x36, Module.Enemies(OID.QueensWarrior).FirstOrDefault, 3.3f); + + ComponentCondition(id + 0x40, 1.0f, comp => comp.CurState == AboveBoard.State.ThrowUpDone, "Throw up"); + ComponentCondition(id + 0x41, 2.0f, comp => comp.CurState == AboveBoard.State.ShortExplosionsDone, "Bombs 1"); + ComponentCondition(id + 0x42, 4.2f, comp => comp.CurState == AboveBoard.State.LongExplosionsDone, "Bombs 2") + .DeactivateOnExit(); + + Targetable(id + 0x50, true, 3.0f, "Boss reappears"); + } + + private void RelentlessPlay5(uint id, float delay) + { + Cast(id, AID.RelentlessPlay, delay, 5); + + ActorCastStart(id + 0x10, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.SoftEnrageW, 3.1f); + ActorCastStart(id + 0x11, Module.Enemies(OID.QueensSoldier).FirstOrDefault, AID.SoftEnrageS, 3); + ActorCastEnd(id + 0x12, Module.Enemies(OID.QueensWarrior).FirstOrDefault, 2, false, "Raidwide 1") + .SetHint(StateMachine.StateHint.Raidwide); + ActorCastEnd(id + 0x13, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 3, false, "Raidwide 2") + .SetHint(StateMachine.StateHint.Raidwide); + + ActorCastStart(id + 0x20, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.SoftEnrageW, 0.1f); + ActorCastStart(id + 0x21, Module.Enemies(OID.QueensSoldier).FirstOrDefault, AID.SoftEnrageS, 3); + ActorCastEnd(id + 0x22, Module.Enemies(OID.QueensWarrior).FirstOrDefault, 2, false, "Raidwide 3") + .SetHint(StateMachine.StateHint.Raidwide); + CastStart(id + 0x23, AID.EmpyreanIniquity, 0.8f); + ActorCastEnd(id + 0x24, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 2.2f, false, "Raidwide 4") + .SetHint(StateMachine.StateHint.Raidwide); + CastEnd(id + 0x25, 2.8f, "Raidwide 5") + .SetHint(StateMachine.StateHint.Raidwide); + + ActorCastStart(id + 0x30, Module.Enemies(OID.QueensKnight).FirstOrDefault, AID.SoftEnrageK, 1.3f); + ActorCastStart(id + 0x31, Module.Enemies(OID.QueensWarrior).FirstOrDefault, AID.SoftEnrageW, 3); + ActorCastEnd(id + 0x32, Module.Enemies(OID.QueensKnight).FirstOrDefault, 2, false, "Raidwide 6") + .SetHint(StateMachine.StateHint.Raidwide); + ActorCastStart(id + 0x33, Module.Enemies(OID.QueensSoldier).FirstOrDefault, AID.SoftEnrageS, 1); + ActorCastEnd(id + 0x34, Module.Enemies(OID.QueensWarrior).FirstOrDefault, 2, false, "Raidwide 7") + .SetHint(StateMachine.StateHint.Raidwide); + ActorCastStart(id + 0x35, Module.Enemies(OID.QueensGunner).FirstOrDefault, AID.SoftEnrageG, 1); + ActorCastEnd(id + 0x36, Module.Enemies(OID.QueensSoldier).FirstOrDefault, 2, false, "Raidwide 8") + .SetHint(StateMachine.StateHint.Raidwide); + ActorCastEnd(id + 0x37, Module.Enemies(OID.QueensGunner).FirstOrDefault, 3, false, "Raidwide 9") + .SetHint(StateMachine.StateHint.Raidwide); + } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/FieryIcyPortent.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/FieryIcyPortent.cs new file mode 100644 index 0000000000..52db6066ce --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/FieryIcyPortent.cs @@ -0,0 +1,26 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; + +class FieryIcyPortent : Components.StayMove +{ + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + var req = (AID)spell.Action.ID switch + { + AID.FieryPortent => Requirement.Stay, + AID.IcyPortent => Requirement.Move, + _ => Requirement.None + }; + if (req != Requirement.None) + { + Array.Fill(Requirements, req); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.FieryPortent or AID.IcyPortent) + { + Array.Fill(Requirements, Requirement.None); + } + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/HeavensWrath.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/HeavensWrath.cs new file mode 100644 index 0000000000..fbb377578b --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/HeavensWrath.cs @@ -0,0 +1,34 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; + +class HeavensWrathAOE : Components.SelfTargetedAOEs +{ + public HeavensWrathAOE() : base(ActionID.MakeSpell(AID.HeavensWrathVisual), new AOEShapeRect(25, 5, 25)) { } +} + +// TODO: generalize +class HeavensWrathKnockback : Components.Knockback +{ + private List _sources = new(); + private static readonly AOEShapeCone _shape = new(30, 90.Degrees()); + + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => _sources; + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.HeavensWrathVisual) + { + _sources.Clear(); + _sources.Add(new(caster.Position, 15, spell.NPCFinishAt, _shape, spell.Rotation + 90.Degrees(), Kind.DirForward)); + _sources.Add(new(caster.Position, 15, spell.NPCFinishAt, _shape, spell.Rotation - 90.Degrees(), Kind.DirForward)); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.HeavensWrathVisual) + { + _sources.Clear(); + ++NumCasts; + } + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/JudgmentBlade.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/JudgmentBlade.cs similarity index 93% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/JudgmentBlade.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/JudgmentBlade.cs index 80e579000d..31d91369e8 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/JudgmentBlade.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/JudgmentBlade.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; class JudgmentBlade : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/MaelstromsBolt.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/MaelstromsBolt.cs similarity index 93% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/MaelstromsBolt.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/MaelstromsBolt.cs index 95e56acfcf..2675458414 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/MaelstromsBolt.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/MaelstromsBolt.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; // TODO: show reflect hints, show stay under dome hints class MaelstromsBolt : Components.CastCounter diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/TurretsTour.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/TurretsTour.cs similarity index 93% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/TurretsTour.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/TurretsTour.cs index f4c8bab7e8..c9d231a4ec 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/TurretsTour.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/TurretsTour.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; class TurretsTour : Components.GenericAOEs { @@ -6,8 +6,6 @@ class TurretsTour : Components.GenericAOEs private List<(Actor caster, AOEShapeRect shape)> _casters = new(); private DateTime _activation; - private static readonly AOEShapeRect _turretShape = new(50, 3); - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { foreach (var t in _turrets) diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/UnluckyLotAetherialSphere.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/UnluckyLotAetherialSphere.cs similarity index 92% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/UnluckyLotAetherialSphere.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/UnluckyLotAetherialSphere.cs index 2695387665..290c82512e 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/UnluckyLotAetherialSphere.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/UnluckyLotAetherialSphere.cs @@ -1,4 +1,4 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; class UnluckyLotAetherialSphere : Components.GenericAOEs { diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/WindsOfWeight.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/WindsOfWeight.cs similarity index 90% rename from BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/WindsOfWeight.cs rename to BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/WindsOfWeight.cs index e38081ed2a..786d95b327 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS7Queen/WindsOfWeight.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginae/DRS8Queen/WindsOfWeight.cs @@ -1,6 +1,6 @@ -namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS7Queen; +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS8Queen; -// TODO: this is essentialy copy-paste of DRS3 component, generalize?.. the only different thing is AIDs +// TODO: this is essentialy copy-paste of DRS4 component, generalize?.. the only different thing is AIDs class WindsOfWeight : Components.GenericAOEs { private List _green = new(); diff --git a/BossMod/Network/IDScramble.cs b/BossMod/Network/IDScramble.cs index 49fa15d44d..b72b8a36b5 100644 --- a/BossMod/Network/IDScramble.cs +++ b/BossMod/Network/IDScramble.cs @@ -23,6 +23,6 @@ public static void Initialize() Service.Log($"IDScramble address = 0x{scrambleAddr:X}"); OffsetBaseChanging = (int*)scrambleAddr; OffsetAdjusted = OffsetBaseChanging + 1; - OffsetBaseFixed = OffsetBaseChanging + 3; + OffsetBaseFixed = OffsetBaseChanging + 2; } } diff --git a/BossMod/Replay/Analysis/StateTransitionTimings.cs b/BossMod/Replay/Analysis/StateTransitionTimings.cs index cfa1ef9cde..3db82b3cf8 100644 --- a/BossMod/Replay/Analysis/StateTransitionTimings.cs +++ b/BossMod/Replay/Analysis/StateTransitionTimings.cs @@ -22,7 +22,7 @@ public TransitionMetric(double duration, Replay replay, Replay.Encounter encount class TransitionMetrics { - public double MinTime = Double.MaxValue; + public double MinTime = double.MaxValue; public double MaxTime; public double AvgTime; public double StdDev; @@ -42,16 +42,11 @@ public StateMetrics(string name, double expectedTime) } } - class MetricsToRemove - { - public List<(StateMetrics state, uint to, TransitionMetrics trans, TransitionMetric metric)> Instances = new(); - public List<(StateMetrics state, uint to)> Transitions = new(); - } - private SortedDictionary _metrics = new(); private List<(Replay, Replay.Encounter, Replay.EncounterError)> _errors = new(); private List<(Replay, Replay.Encounter)> _encounters = new(); private float _lastSecondsToIgnore = 0; + private object? _selected; public StateTransitionTimings(List replays, uint oid) { @@ -88,7 +83,6 @@ public StateTransitionTimings(List replays, uint oid) { foreach (var (_, trans) in state.Transitions) { - trans.Instances.SortBy(e => e.Duration); RecalculateMetrics(trans); } } @@ -109,7 +103,7 @@ public void Draw(UITree tree) tree.LeafNodes(_errors.Where(e => (e.Item2.Time.End - e.Item3.Timestamp).TotalSeconds >= _lastSecondsToIgnore), error => $"{LocationString(error.Item1, error.Item2, error.Item3.Timestamp)} [{error.Item3.CompType}] {error.Item3.Message}"); } - MetricsToRemove delContext = new(); + List actions = new(); foreach (var from in _metrics.Values) { Func, UITree.NodeProperties> map = kv => @@ -121,22 +115,25 @@ public void Draw(UITree tree) bool warn = Math.Abs(from.ExpectedTime - kv.Value.AvgTime) > Math.Ceiling(kv.Value.StdDev * 10) / 10; return new($"{name}: {value}###{name}", false, warn ? 0xff00ffff : 0xffffffff); }; - foreach (var (toID, m) in tree.Nodes(from.Transitions, map, kv => TransitionContextMenu(from, kv.Key, delContext))) + foreach (var (toID, m) in tree.Nodes(from.Transitions, map, kv => TransitionContextMenu(from, kv.Key, kv.Value, tree, actions), select: kv => _selected = kv.Value)) { foreach (var inst in m.Instances) { bool warn = Math.Abs(inst.Duration - m.AvgTime) > m.StdDev; - tree.LeafNode($"{inst.Duration:f2}: {LocationString(inst.Replay, inst.Encounter, inst.Time)}", warn ? 0xff00ffff : 0xffffffff, () => TransitionInstanceContextMenu(from, toID, m, inst, delContext)); + tree.LeafNode($"{inst.Duration:f2}: {LocationString(inst.Replay, inst.Encounter, inst.Time)}", warn ? 0xff00ffff : 0xffffffff, () => TransitionInstanceContextMenu(from, toID, m, inst, actions), select: () => _selected = inst); } } } - ExecuteDeletes(delContext); + + foreach (var a in actions) + a(); } private string LocationString(Replay replay, Replay.Encounter enc, DateTime timestamp) => $"{replay.Path} @ {enc.Time.Start:O} + {(timestamp - enc.Time.Start).TotalSeconds:f3} / {enc.Time.Duration:f3}"; private void RecalculateMetrics(TransitionMetrics trans) { + trans.Instances.SortBy(e => e.Duration); trans.MinTime = trans.Instances.First().Duration; trans.MaxTime = trans.Instances.Last().Duration; double sum = 0, sumSq = 0; @@ -149,36 +146,39 @@ private void RecalculateMetrics(TransitionMetrics trans) trans.StdDev = trans.Instances.Count > 0 ? Math.Sqrt((sumSq - sum * sum / trans.Instances.Count) / (trans.Instances.Count - 1)) : 0; } - private void ExecuteDeletes(MetricsToRemove delContext) + private void TransitionContextMenu(StateMetrics state, uint to, TransitionMetrics trans, UITree tree, List actions) { - // note: we can't delete collection elements while iterating - foreach (var e in delContext.Instances) - { - e.trans.Instances.Remove(e.metric); - if (e.trans.Instances.Count > 0) - RecalculateMetrics(e.trans); - else - e.state.Transitions.Remove(e.to); - } - foreach (var e in delContext.Transitions) + if (ImGui.MenuItem("Ignore this transition")) { - e.state.Transitions.Remove(e.to); + actions.Add(() => + { + if (_selected == trans) + _selected = null; + state.Transitions.Remove(to); + }); } - } - - private void TransitionContextMenu(StateMetrics state, uint to, MetricsToRemove delContext) - { - if (ImGui.MenuItem("Ignore this transition")) + if (_selected is TransitionMetrics dest && trans != dest && ImGui.MenuItem("Merge to selected transition")) { - delContext.Transitions.Add((state, to)); + dest.Instances.AddRange(trans.Instances); + RecalculateMetrics(dest); + state.Transitions.Remove(to); } } - private void TransitionInstanceContextMenu(StateMetrics state, uint to, TransitionMetrics trans, TransitionMetric metric, MetricsToRemove delContext) + private void TransitionInstanceContextMenu(StateMetrics state, uint to, TransitionMetrics trans, TransitionMetric metric, List actions) { if (ImGui.MenuItem("Ignore this instance")) { - delContext.Instances.Add((state, to, trans, metric)); + actions.Add(() => + { + if (_selected == metric) + _selected = null; + trans.Instances.Remove(metric); + if (trans.Instances.Count > 0) + RecalculateMetrics(trans); + else + state.Transitions.Remove(to); + }); } } }