From 0699ab384f604367ea66b5744fc208ed3b8b6110 Mon Sep 17 00:00:00 2001 From: CarnifexOptimus <156172553+CarnifexOptimus@users.noreply.github.com> Date: Thu, 30 Jan 2025 10:39:06 +0100 Subject: [PATCH] some quest module improvements --- BossMod/BossModule/AIHintsBuilder.cs | 2 +- BossMod/BossModule/BossModule.cs | 144 ++++++++++++------ BossMod/BossModule/BossModuleMainWindow.cs | 2 +- BossMod/BossModule/SimpleBossModule.cs | 2 + BossMod/Components/Tethers.cs | 18 ++- BossMod/Config/ConfigNode.cs | 5 +- BossMod/Config/ConfigRoot.cs | 3 +- .../Alliance/A14ShadowLord/A14ShadowLord.cs | 2 +- .../Dawntrail/FATE/MicaTheMagicalMu.cs | 5 +- .../FATE/TheSerpentlordSeethes/Ttokrrone.cs | 8 +- BossMod/Modules/Endwalker/FATE/Chi.cs | 6 +- BossMod/Modules/Endwalker/FATE/Daivadipa.cs | 5 +- .../MSQ/AnUnforeseenBargain/P2Andromalius.cs | 19 ++- .../AsTheHeavensBurn/P1TerminusIdolizer.cs | 6 +- .../Quest/MSQ/WhereEverythingBegins/P1.cs | 3 +- .../Quest/MSQ/WhereEverythingBegins/P2.cs | 19 ++- .../Dungeon/D15Xelphatol/D151NuzalHueloc.cs | 23 ++- .../Quest/MSQ/DivineIntervention.cs | 19 ++- .../Dungeon/D14Praetorium/D142Nero.cs | 11 +- .../Dungeon/D14Praetorium/D143Gaius.cs | 13 +- .../Shadowbringers/FATE/Archaeotania.cs | 5 +- .../Quest/MSQ/TheGreatShipVylbrand.cs | 61 +++++++- .../SleepNowInSapphire/P1GuidanceSystem.cs | 14 +- .../SleepNowInSapphire/P2SapphireWeapon.cs | 73 +++++++-- .../D150ScholaMarkIIColossus.cs | 20 ++- .../SideQuests/SleepNowInSapphire.cs | 7 +- 26 files changed, 362 insertions(+), 133 deletions(-) diff --git a/BossMod/BossModule/AIHintsBuilder.cs b/BossMod/BossModule/AIHintsBuilder.cs index 2c5dd92fb4..782885699b 100644 --- a/BossMod/BossModule/AIHintsBuilder.cs +++ b/BossMod/BossModule/AIHintsBuilder.cs @@ -50,7 +50,7 @@ public void Update(AIHints hints, int playerSlot, float maxCastTime) FillEnemies(hints, playerAssignment == PartyRolesConfig.Assignment.MT || playerAssignment == PartyRolesConfig.Assignment.OT && !_ws.Party.WithoutSlot(false, false, true).Any(p => p != player && p.Role == Role.Tank)); if (activeModule != null) { - activeModule.CalculateAIHints(playerSlot, player, playerAssignment, hints); + activeModule.CalculateAIHints(playerSlot, ref player, ref playerAssignment, ref hints); } else { diff --git a/BossMod/BossModule/BossModule.cs b/BossMod/BossModule/BossModule.cs index 198c3e2753..260493fcdb 100644 --- a/BossMod/BossModule/BossModule.cs +++ b/BossMod/BossModule/BossModule.cs @@ -63,7 +63,6 @@ public List Enemies(ReadOnlySpan enemies) } return null; } - private int componentCount; public void ActivateComponent() where T : BossComponent { @@ -74,7 +73,6 @@ public void ActivateComponent() where T : BossComponent } var comp = New.Create(this); Components.Add(comp); - ++componentCount; // execute callbacks for existing state foreach (var actor in WorldState.Actors) @@ -83,28 +81,44 @@ public void ActivateComponent() where T : BossComponent if (nonPlayer) { comp.OnActorCreated(actor); - if (actor.CastInfo?.IsSpell() ?? false) - comp.OnCastStarted(actor, actor.CastInfo); + ref var castinfo = ref actor.CastInfo; + if (castinfo?.IsSpell() ?? false) + comp.OnCastStarted(actor, castinfo); + } + ref var tether = ref actor.Tether; + if (tether.ID != 0) + comp.OnTethered(actor, tether); + var len = actor.Statuses.Length; + for (var i = 0; i < len; ++i) + { + ref var status = ref actor.Statuses[i]; + if (status.ID != 0) + comp.OnStatusGain(actor, status); } - if (actor.Tether.ID != 0) - comp.OnTethered(actor, actor.Tether); - for (var i = 0; i < actor.Statuses.Length; ++i) - if (actor.Statuses[i].ID != 0) - comp.OnStatusGain(actor, actor.Statuses[i]); } } public void DeactivateComponent() where T : BossComponent { - var count = Components.RemoveAll(x => x is T); - componentCount -= count; - if (count == 0) + var count = Components.Count; + var removed = false; + for (var i = 0; i < count; ++i) + { + var comp = Components[i]; + if (comp is T) + { + Components.Remove(comp); + removed = true; + break; + } + } + if (!removed) ReportError(null, $"State {StateMachine.ActiveState?.ID:X}: Could not find a component of type {typeof(T)} to deactivate"); } public void ClearComponents(Predicate condition) { - componentCount -= Components.RemoveAll(condition); + Components.RemoveAll(condition); } protected BossModule(WorldState ws, Actor primary, WPos center, ArenaBounds bounds) @@ -164,7 +178,8 @@ public void Update() if (StateMachine.ActiveState != null) { UpdateModule(); - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].Update(); } } @@ -185,22 +200,23 @@ public void Draw(Angle cameraAzimuth, int pcSlot, bool includeText, bool include DrawGlobalHints(CalculateGlobalHints()); if (WindowConfig.ShowPlayerHints) - DrawPlayerHints(pcHints); + DrawPlayerHints(ref pcHints); } if (includeArena) { Arena.Begin(cameraAzimuth); - DrawArena(pcSlot, pc, pcHints.Any(h => h.Item2)); + DrawArena(pcSlot, ref pc, pcHints.Any(h => h.Item2)); MiniArena.End(); } } - public virtual void DrawArena(int pcSlot, Actor pc, bool haveRisks) + public virtual void DrawArena(int pcSlot, ref Actor pc, bool haveRisks) { // draw background DrawArenaBackground(pcSlot, pc); - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].DrawArenaBackground(pcSlot, pc); // draw borders @@ -212,11 +228,11 @@ public virtual void DrawArena(int pcSlot, Actor pc, bool haveRisks) DrawWaymarks(); // draw non-player alive party members - DrawPartyMembers(pcSlot, pc); + DrawPartyMembers(pcSlot, ref pc); // draw foreground DrawArenaForeground(pcSlot, pc); - for (var i = 0; i < componentCount; ++i) + for (var i = 0; i < count; ++i) Components[i].DrawArenaForeground(pcSlot, pc); if (WindowConfig.ShowMeleeRangeIndicator) { @@ -232,15 +248,17 @@ public virtual void DrawArena(int pcSlot, Actor pc, bool haveRisks) public BossComponent.TextHints CalculateHintsForRaidMember(int slot, Actor actor) { BossComponent.TextHints hints = []; - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].AddHints(slot, actor, hints); return hints; } - public BossComponent.MovementHints CalculateMovementHintsForRaidMember(int slot, Actor actor) + public BossComponent.MovementHints CalculateMovementHintsForRaidMember(int slot, ref Actor actor) { BossComponent.MovementHints hints = []; - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].AddMovementHints(slot, actor, hints); return hints; } @@ -248,16 +266,18 @@ public BossComponent.MovementHints CalculateMovementHintsForRaidMember(int slot, public BossComponent.GlobalHints CalculateGlobalHints() { BossComponent.GlobalHints hints = []; - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].AddGlobalHints(hints); return hints; } - public void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + public void CalculateAIHints(int slot, ref Actor actor, ref PartyRolesConfig.Assignment assignment, ref AIHints hints) { hints.PathfindMapCenter = Center; hints.PathfindMapBounds = Bounds; - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].AddAIHints(slot, actor, assignment, hints); CalculateModuleAIHints(slot, actor, assignment, hints); if (!WindowConfig.AllowAutomaticActions && AI.AIManager.Instance?.Beh == null) @@ -304,7 +324,7 @@ private void DrawGlobalHints(BossComponent.GlobalHints hints) ImGui.NewLine(); } - private void DrawPlayerHints(BossComponent.TextHints hints) + private void DrawPlayerHints(ref BossComponent.TextHints hints) { foreach ((var hint, var risk) in hints) { @@ -337,11 +357,11 @@ private void DrawWaymark(Vector3? pos, string text, uint color) } } - private void DrawPartyMembers(int pcSlot, Actor pc) + private void DrawPartyMembers(int pcSlot, ref Actor pc) { foreach (var (slot, player) in Raid.WithSlot().Exclude(pcSlot)) { - var (prio, color) = CalculateHighestPriority(pcSlot, pc, slot, player); + var (prio, color) = CalculateHighestPriority(pcSlot, ref pc, slot, player); var isFocus = WorldState.Client.FocusTargetId == player.InstanceID; if (prio == BossComponent.PlayerPriority.Irrelevant && !WindowConfig.ShowIrrelevantPlayers && !(isFocus && WindowConfig.ShowFocusTargetPlayer)) @@ -382,11 +402,12 @@ private void DrawPartyMembers(int pcSlot, Actor pc) } } - private (BossComponent.PlayerPriority, uint) CalculateHighestPriority(int pcSlot, Actor pc, int playerSlot, Actor player) + private (BossComponent.PlayerPriority, uint) CalculateHighestPriority(int pcSlot, ref Actor pc, int playerSlot, Actor player) { uint color = 0; var highestPrio = BossComponent.PlayerPriority.Irrelevant; - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) { uint subColor = 0; var subPrio = Components[i].CalcPriority(pcSlot, pc, playerSlot, player, ref subColor); @@ -403,109 +424,136 @@ private void OnActorCreated(Actor actor) { RelevantEnemies.GetValueOrDefault(actor.OID)?.Add(actor); if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy) - for (var i = 0; i < componentCount; ++i) + { + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnActorCreated(actor); + } } private void OnActorDestroyed(Actor actor) { RelevantEnemies.GetValueOrDefault(actor.OID)?.Remove(actor); if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy) - for (var i = 0; i < componentCount; ++i) + { + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnActorDestroyed(actor); + } } private void OnActorCastStarted(Actor actor) { if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy && (actor.CastInfo?.IsSpell() ?? false)) - for (var i = 0; i < componentCount; ++i) + { + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnCastStarted(actor, actor.CastInfo); + } } private void OnActorCastFinished(Actor actor) { if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy && (actor.CastInfo?.IsSpell() ?? false)) - for (var i = 0; i < componentCount; ++i) + { + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnCastFinished(actor, actor.CastInfo); + } } private void OnActorTethered(Actor actor) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnTethered(actor, actor.Tether); } private void OnActorUntethered(Actor actor) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnUntethered(actor, actor.Tether); } private void OnActorStatusGain(Actor actor, int index) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnStatusGain(actor, actor.Statuses[index]); } private void OnActorStatusLose(Actor actor, int index) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnStatusLose(actor, actor.Statuses[index]); } private void OnActorIcon(Actor actor, uint iconID, ulong targetID) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnEventIcon(actor, iconID, targetID); } private void OnActorCastEvent(Actor actor, ActorCastEvent cast) { if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy && cast.IsSpell()) - for (var i = 0; i < componentCount; ++i) + { + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnEventCast(actor, cast); + } } private void OnActorEState(Actor actor, ushort state) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnActorEState(actor, state); } private void OnActorEAnim(Actor actor, ushort p1, ushort p2) { var state = ((uint)p1 << 16) | p2; - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnActorEAnim(actor, state); } private void OnActorPlayActionTimelineEvent(Actor actor, ushort id) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnActorPlayActionTimelineEvent(actor, id); } private void OnActorNpcYell(Actor actor, ushort id) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnActorNpcYell(actor, id); } private void OnActorModelStateChange(Actor actor) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnActorModelStateChange(actor, actor.ModelState.ModelState, actor.ModelState.AnimState1, actor.ModelState.AnimState2); } private void OnEnvControl(WorldState.OpEnvControl op) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnEventEnvControl(op.Index, op.State); } private void OnDirectorUpdate(WorldState.OpDirectorUpdate op) { - for (var i = 0; i < componentCount; ++i) + var count = Components.Count; + for (var i = 0; i < count; ++i) Components[i].OnEventDirectorUpdate(op.UpdateID, op.Param1, op.Param2, op.Param3, op.Param4); } } diff --git a/BossMod/BossModule/BossModuleMainWindow.cs b/BossMod/BossModule/BossModuleMainWindow.cs index a14ca45642..3ccfa4e3ed 100644 --- a/BossMod/BossModule/BossModuleMainWindow.cs +++ b/BossMod/BossModule/BossModuleMainWindow.cs @@ -32,7 +32,7 @@ public override void PreOpenCheck() ForceMainWindow = _mgr.Config.TrishaMode; // NoBackground flag without ForceMainWindow works incorrectly for whatever reason if (_mgr.Config.ShowWorldArrows && _mgr.ActiveModule != null && _mgr.WorldState.Party[PartyState.PlayerSlot] is var pc && pc != null) - DrawMovementHints(_mgr.ActiveModule.CalculateMovementHintsForRaidMember(PartyState.PlayerSlot, pc), pc.PosRot.Y); + DrawMovementHints(_mgr.ActiveModule.CalculateMovementHintsForRaidMember(PartyState.PlayerSlot, ref pc), pc.PosRot.Y); } public override void OnOpen() diff --git a/BossMod/BossModule/SimpleBossModule.cs b/BossMod/BossModule/SimpleBossModule.cs index b10cfb05ff..7c10e08700 100644 --- a/BossMod/BossModule/SimpleBossModule.cs +++ b/BossMod/BossModule/SimpleBossModule.cs @@ -8,6 +8,8 @@ public override bool CheckReset() => !PrimaryActor.InCombat; + protected override bool CheckPull() => base.CheckPull() && (Center - Raid.Player()!.Position).LengthSq() < 900; + protected override void UpdateModule() { Arena.Center = WorldState.Party.Player()?.Position ?? default; diff --git a/BossMod/Components/Tethers.cs b/BossMod/Components/Tethers.cs index d1b7576ea2..d131f0adb6 100644 --- a/BossMod/Components/Tethers.cs +++ b/BossMod/Components/Tethers.cs @@ -112,9 +112,9 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether) } // generic component for AOE at tethered targets; players are supposed to intercept tethers and gtfo from the raid -public class InterceptTetherAOE(BossModule module, ActionID aid, uint tetherID, float radius) : CastCounter(module, aid) +public class InterceptTetherAOE(BossModule module, ActionID aid, uint tetherID, float radius, uint[]? excludedAllies = null) : CastCounter(module, aid) { - // TODO: add forbidden players/NPCs logic + public readonly uint[]? ExcludedAllies = excludedAllies; public readonly uint TID = tetherID; public readonly float Radius = radius; public readonly List<(Actor Player, Actor Enemy)> Tethers = []; @@ -122,7 +122,7 @@ public class InterceptTetherAOE(BossModule module, ActionID aid, uint tetherID, private BitMask _inAnyAOE; // players hit by aoe, excluding selves public DateTime Activation; - public bool Active => _tetheredPlayers.Any(); + public bool Active => Tethers.Count != 0; public override void Update() { @@ -187,10 +187,15 @@ public override void DrawArenaForeground(int pcSlot, Actor pc) var count = Tethers.Count; if (count == 0) return; + var len = ExcludedAllies?.Length; + var exclude = new List(len ?? 0); + if (ExcludedAllies != null) + for (var i = 0; i < len; ++i) + exclude.AddRange(Module.Enemies(ExcludedAllies[i])); for (var i = 0; i < count; ++i) { var side = Tethers[i]; - Arena.AddLine(side.Enemy.Position, side.Player.Position, side.Player.OID == 0 ? Colors.Safe : 0); + Arena.AddLine(side.Enemy.Position, side.Player.Position, Raid.WithoutSlot().Exclude(exclude).Contains(side.Player) ? Colors.Safe : 0); Arena.AddCircle(side.Player.Position, Radius); } } @@ -254,9 +259,10 @@ public override void DrawArenaForeground(int pcSlot, Actor pc) { if (!Active) return; - var exclude = new List { }; + var len = ExcludedAllies?.Length; + var exclude = new List(len ?? 0); if (ExcludedAllies != null) - for (var i = 0; i < ExcludedAllies.Length; ++i) + for (var i = 0; i < len; ++i) exclude.AddRange(Module.Enemies(ExcludedAllies[i])); for (var i = 0; i < _tethers.Count; ++i) { diff --git a/BossMod/Config/ConfigNode.cs b/BossMod/Config/ConfigNode.cs index 895d437a16..6879cf5bb2 100644 --- a/BossMod/Config/ConfigNode.cs +++ b/BossMod/Config/ConfigNode.cs @@ -107,9 +107,10 @@ public virtual void Serialize(Utf8JsonWriter writer, JsonSerializerOptions optio writer.WriteStartObject(); var fields = GetSerializableFields(GetType()); - for (var i = 0; i < fields.Length; ++i) + var len = fields.Length; + for (var i = 0; i < len; ++i) { - var field = fields[i]; + ref var field = ref fields[i]; var fieldValue = field.GetValue(this); writer.WritePropertyName(field.Name); diff --git a/BossMod/Config/ConfigRoot.cs b/BossMod/Config/ConfigRoot.cs index 3a7b1f6fd0..eddb8331c8 100644 --- a/BossMod/Config/ConfigRoot.cs +++ b/BossMod/Config/ConfigRoot.cs @@ -7,7 +7,6 @@ namespace BossMod; public class ConfigRoot { public Event Modified = new(); - private const int _version = 10; public readonly Dictionary _nodes = []; public List Nodes => [.. _nodes.Values]; @@ -70,7 +69,7 @@ public void SaveToFile(FileInfo file) using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); writer.WriteStartObject(); - writer.WriteNumber("Version", _version); + writer.WriteNumber("Version", ConfigConverter.Schema.CurrentVersion); writer.WritePropertyName("Payload"); writer.WriteStartObject(); diff --git a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs index 5643d5f22c..f6bdf9962b 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs @@ -14,7 +14,7 @@ public class A14ShadowLord(WorldState ws, Actor primary) : BossModule(ws, primar private const int Edges = 64; public static readonly WPos ArenaCenter = new(150, 800); public static readonly ArenaBoundsCircle DefaultBounds = new(30); - public static readonly Polygon[] Circles = [new(new(166.249f, 800), RadiusSmall, Edges), new(new(133.783f, 800), RadiusSmall, Edges), + public static readonly Polygon[] Circles = [new(new(166.251f, 800), RadiusSmall, Edges), new(new(133.788f, 800), RadiusSmall, Edges), new(new(150, 816.227f), RadiusSmall, Edges), new(new(150, 783.812f), RadiusSmall, Edges)]; // the circle coordinates are not perfectly placed for some reason, got these from analyzing the collision data private static readonly RectangleSE[] rects = [new(Circles[1].Center, Circles[2].Center, HalfWidth), new(Circles[1].Center, Circles[3].Center, HalfWidth), new(Circles[3].Center, Circles[0].Center, HalfWidth), new(Circles[0].Center, Circles[2].Center, HalfWidth)]; diff --git a/BossMod/Modules/Dawntrail/FATE/MicaTheMagicalMu.cs b/BossMod/Modules/Dawntrail/FATE/MicaTheMagicalMu.cs index dd78bb0b89..2828a191f9 100644 --- a/BossMod/Modules/Dawntrail/FATE/MicaTheMagicalMu.cs +++ b/BossMod/Modules/Dawntrail/FATE/MicaTheMagicalMu.cs @@ -261,4 +261,7 @@ public MicaTheMagicalMuStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.Fate, GroupID = 1922, NameID = 13049)] -public class MicaTheMagicalMu(WorldState ws, Actor primary) : BossModule(ws, primary, new(791, 593), new ArenaBoundsRect(20.5f, 19.5f)); +public class MicaTheMagicalMu(WorldState ws, Actor primary) : BossModule(ws, primary, new(791, 593), new ArenaBoundsRect(20.5f, 19.5f)) +{ + protected override bool CheckPull() => base.CheckPull() && (Center - Raid.Player()!.Position).LengthSq() < 420; +} diff --git a/BossMod/Modules/Dawntrail/FATE/TheSerpentlordSeethes/Ttokrrone.cs b/BossMod/Modules/Dawntrail/FATE/TheSerpentlordSeethes/Ttokrrone.cs index a47e275b7a..55c8f383a0 100644 --- a/BossMod/Modules/Dawntrail/FATE/TheSerpentlordSeethes/Ttokrrone.cs +++ b/BossMod/Modules/Dawntrail/FATE/TheSerpentlordSeethes/Ttokrrone.cs @@ -96,9 +96,9 @@ public TtokrroneStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.Fate, GroupID = 1871, NameID = 12733)] -public class Ttokrrone(WorldState ws, Actor primary) : BossModule(ws, primary, new(53, -820), new ArenaBoundsCircle(29.5f)) +public class Ttokrrone(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) { - // if boss is pulled when player is really far away and helpers aren't loaded, some components might never see resolve casts and get stuck forever - protected override bool CheckPull() => base.CheckPull() && Enemies(OID.Helper).Count != 0; -} + private static readonly ArenaBoundsComplex arena = new([new Polygon(new(53, -820), 29.5f, 48)]); + protected override bool CheckPull() => base.CheckPull() && (arena.Center - Raid.Player()!.Position).LengthSq() < 900; +} diff --git a/BossMod/Modules/Endwalker/FATE/Chi.cs b/BossMod/Modules/Endwalker/FATE/Chi.cs index 1749e5ed0c..dd61000b5e 100644 --- a/BossMod/Modules/Endwalker/FATE/Chi.cs +++ b/BossMod/Modules/Endwalker/FATE/Chi.cs @@ -260,4 +260,8 @@ public ChiStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.Fate, GroupID = 1855, NameID = 10400)] -public class Chi(WorldState ws, Actor primary) : BossModule(ws, primary, new(650, 0), new ArenaBoundsSquare(29.5f)); +public class Chi(WorldState ws, Actor primary) : BossModule(ws, primary, new(650, 0), new ArenaBoundsSquare(29.5f)) +{ + protected override bool CheckPull() => base.CheckPull() && (Center - Raid.Player()!.Position).LengthSq() < 900; +} + diff --git a/BossMod/Modules/Endwalker/FATE/Daivadipa.cs b/BossMod/Modules/Endwalker/FATE/Daivadipa.cs index 682212a23f..365cd072ba 100644 --- a/BossMod/Modules/Endwalker/FATE/Daivadipa.cs +++ b/BossMod/Modules/Endwalker/FATE/Daivadipa.cs @@ -227,4 +227,7 @@ public DaivadipaStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.Fate, GroupID = 1763, NameID = 10269)] -public class Daivadipa(WorldState ws, Actor primary) : BossModule(ws, primary, new(-608, 811), new ArenaBoundsSquare(24.5f)); +public class Daivadipa(WorldState ws, Actor primary) : BossModule(ws, primary, new(-608, 811), new ArenaBoundsSquare(24.5f)) +{ + protected override bool CheckPull() => base.CheckPull() && (Center - Raid.Player()!.Position).LengthSq() < 625; +} diff --git a/BossMod/Modules/Endwalker/Quest/MSQ/AnUnforeseenBargain/P2Andromalius.cs b/BossMod/Modules/Endwalker/Quest/MSQ/AnUnforeseenBargain/P2Andromalius.cs index 77339240c5..293813bbbf 100644 --- a/BossMod/Modules/Endwalker/Quest/MSQ/AnUnforeseenBargain/P2Andromalius.cs +++ b/BossMod/Modules/Endwalker/Quest/MSQ/AnUnforeseenBargain/P2Andromalius.cs @@ -121,8 +121,7 @@ class Shield(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(5, true); private AOEInstance? _aoe; - private const string RiskHint = "Go under shield!"; - private const string StayHint = "Wait under shield!"; + private const string Hint = "Go under shield!"; public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); @@ -136,10 +135,20 @@ public override void AddHints(int slot, Actor actor, TextHints hints) { if (_aoe == null) return; - if (ActiveAOEs(slot, actor).Any(c => !c.Check(actor.Position))) - hints.Add(RiskHint); + + var shouldAddHint = false; + foreach (var c in ActiveAOEs(slot, actor)) + { + if (!c.Check(actor.Position)) + { + shouldAddHint = true; + break; + } + } + if (shouldAddHint) + hints.Add(Hint); else - hints.Add(StayHint, false); + hints.Add(Hint, false); } public override void OnCastFinished(Actor caster, ActorCastInfo spell) diff --git a/BossMod/Modules/Endwalker/Quest/MSQ/AsTheHeavensBurn/P1TerminusIdolizer.cs b/BossMod/Modules/Endwalker/Quest/MSQ/AsTheHeavensBurn/P1TerminusIdolizer.cs index 87b0106b20..515dabe9dc 100644 --- a/BossMod/Modules/Endwalker/Quest/MSQ/AsTheHeavensBurn/P1TerminusIdolizer.cs +++ b/BossMod/Modules/Endwalker/Quest/MSQ/AsTheHeavensBurn/P1TerminusIdolizer.cs @@ -52,14 +52,14 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) var count = _aoes.Count; if (count == 0) return []; - List aoes = new(count); + var aoes = new AOEInstance[count]; for (var i = 0; i < count; ++i) { var aoe = _aoes[i]; if (i == 0) - aoes.Add(count > 1 ? aoe with { Color = Colors.Danger } : aoe); + aoes[i] = count > 1 ? aoe with { Color = Colors.Danger } : aoe; else - aoes.Add(aoe with { Risky = false }); + aoes[i] = aoe with { Risky = false }; } return aoes; } diff --git a/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P1.cs b/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P1.cs index 2d7ebc125f..f31dc56757 100644 --- a/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P1.cs +++ b/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P1.cs @@ -53,7 +53,8 @@ public class ScarmiglioneP1(WorldState ws, Actor primary) : BossModule(ws, prima protected override bool CheckPull() { var enemies = Enemies(trash); - for (var i = 0; i < enemies.Count; ++i) + var count = enemies.Count; + for (var i = 0; i < count; ++i) { if (enemies[i].InCombat) return true; diff --git a/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P2.cs b/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P2.cs index eb9ea81d85..7956924336 100644 --- a/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P2.cs +++ b/BossMod/Modules/Endwalker/Quest/MSQ/WhereEverythingBegins/P2.cs @@ -144,8 +144,7 @@ class Shield(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(5, true); private AOEInstance? _aoe; - private const string RiskHint = "Go under shield!"; - private const string StayHint = "Wait under shield!"; + private const string Hint = "Go under shield!"; public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); @@ -165,10 +164,20 @@ public override void AddHints(int slot, Actor actor, TextHints hints) { if (_aoe == null) return; - if (ActiveAOEs(slot, actor).Any(c => !c.Check(actor.Position))) - hints.Add(RiskHint); + + var shouldAddHint = false; + foreach (var c in ActiveAOEs(slot, actor)) + { + if (!c.Check(actor.Position)) + { + shouldAddHint = true; + break; + } + } + if (shouldAddHint) + hints.Add(Hint); else - hints.Add(StayHint, false); + hints.Add(Hint, false); } } diff --git a/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D151NuzalHueloc.cs b/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D151NuzalHueloc.cs index eb9c328f42..2e73195ff4 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D151NuzalHueloc.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D151NuzalHueloc.cs @@ -43,7 +43,7 @@ class HotBlast(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(4, true); private AOEInstance? _aoe; - private const string RiskHint = "Go under boss!"; + private const string Hint = "Go under boss!"; public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); @@ -61,11 +61,22 @@ public override void Update() public override void AddHints(int slot, Actor actor, TextHints hints) { - var activeAOEs = ActiveAOEs(slot, actor).ToList(); - if (activeAOEs.Any(c => !c.Check(actor.Position))) - hints.Add(RiskHint); - else if (activeAOEs.Any(c => c.Check(actor.Position))) - hints.Add(RiskHint, false); + if (_aoe == null) + return; + + var shouldAddHint = false; + foreach (var c in ActiveAOEs(slot, actor)) + { + if (!c.Check(actor.Position)) + { + shouldAddHint = true; + break; + } + } + if (shouldAddHint) + hints.Add(Hint); + else + hints.Add(Hint, false); } } diff --git a/BossMod/Modules/Heavensward/Quest/MSQ/DivineIntervention.cs b/BossMod/Modules/Heavensward/Quest/MSQ/DivineIntervention.cs index 171951eda5..9351958a7c 100644 --- a/BossMod/Modules/Heavensward/Quest/MSQ/DivineIntervention.cs +++ b/BossMod/Modules/Heavensward/Quest/MSQ/DivineIntervention.cs @@ -2,21 +2,30 @@ public enum OID : uint { - Boss = 0x1010, + Boss = 0x1010, // R0.5 IshgardianSteelChain = 0x102C, // R1.0 SerPaulecrainColdfire = 0x1011, // R0.5 ThunderPicket = 0xEC4, // R1.0 - Helper = 0x233C + Helper = 0xE0F } public enum AID : uint { - LightningBolt = 3993, // ThunderPicket->E0F, 2.0s cast, width 4 rect charge + AutoAttack1 = 870, // Boss->Alphinaud, no cast, single-target + AutoAttack2 = 871, // SerPaulecrainColdfire->player, no cast, single-target + + Foresight = 32, // Boss->self, no cast, single-target + LightningBolt = 3993, // ThunderPicket->Helper, 2.0s cast, width 4 rect charge IronTempest = 1003, // Boss->self, 3.5s cast, range 5+R circle Overpower = 720, // Boss->self, 2.5s cast, range 6+R 90-degree cone RingOfFrost = 1316, // SerPaulecrainColdfire->self, 3.0s cast, range 6+R circle Rive = 1135, // Boss->self, 2.5s cast, range 30+R width 2 rect Heartstopper = 866, // SerPaulecrainColdfire->self, 2.5s cast, range 3+R width 3 rect + FirstLesson = 3991, // Boss->100F, no cast, single-target + Bloodbath = 34, // Boss->self, no cast, single-target + BarbaricSurge = 963, // Boss->self, no cast, single-target + ThunderThrust = 3992, // SerPaulecrainColdfire->self, 4.0s cast, range 40 circle + LifeSurge = 83 // SerPaulecrainColdfire->self, no cast, single-target } class LightningBolt(BossModule module) : Components.ChargeAOEs(module, ActionID.MakeSpell(AID.LightningBolt), 2); @@ -25,7 +34,7 @@ class IronTempest(BossModule module) : Components.SimpleAOEs(module, ActionID.Ma class RingOfFrost(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RingOfFrost), 6.5f); class Rive(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Rive), new AOEShapeRect(30.5f, 1)); class Heartstopper(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Heartstopper), new AOEShapeRect(3.5f, 1.5f)); -class Chain(BossModule module) : Components.Adds(module, (uint)OID.IshgardianSteelChain, 1); +class ThunderThrust(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.ThunderThrust)); class SerGrinnauxStates : StateMachineBuilder { @@ -38,7 +47,7 @@ public SerGrinnauxStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() + .ActivateOnEnter() .Raw.Update = () => module.Enemies(SerGrinnaux.Bosses).All(x => x.IsDeadOrDestroyed); } } diff --git a/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D142Nero.cs b/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D142Nero.cs index b061d98bc7..6eb1b56210 100644 --- a/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D142Nero.cs +++ b/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D142Nero.cs @@ -10,10 +10,10 @@ public enum OID : uint public enum AID : uint { AutoAttack = 872, // Boss->player, no cast, single-target + Teleport = 28475, // Boss->location, no cast, single-target IronUprising = 28482, // Boss->self, 3.0s cast, range 7 120-degree cone aoe (knockback 12) SpineShatter = 28483, // Boss->player, 5.0s cast, single-target tankbuster - Teleport = 28475, // Boss->location, no cast, single-target AugmentedSuffering = 28476, // Boss->self, 5.0s cast, knockback 12 AugmentedShatter = 28477, // Boss->player, 5.0s cast, range 6 circle stack AugmentedUprising = 28478, // Boss->self, 7.0s cast, range 45 90-degree cone aoe @@ -51,6 +51,7 @@ public override void OnEventEnvControl(byte index, uint state) if (index == 0x00 && state == 0x00020001) { Arena.Bounds = D142Nero.DefaultBounds; + Arena.Center = D142Nero.DefaultBounds.Center; _aoe = null; begin = true; } @@ -104,16 +105,16 @@ public class D142Nero(WorldState ws, Actor primary) : BossModule(ws, primary, st new(-166.99f, -29.45f), new(-165.38f, -29.56f)]; private static readonly ArenaBoundsComplex startingBounds = new([new PolygonCustom(vertices)]); - public static readonly ArenaBoundsCircle DefaultBounds = new(20); + public static readonly ArenaBoundsComplex DefaultBounds = new([new Polygon(new(-164, 0), 20, 48)]); protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.MagitekDeathClaw => 2, - OID.Boss => 1, + OID.MagitekDeathClaw => 1, _ => 0 }; } diff --git a/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D143Gaius.cs b/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D143Gaius.cs index 5aabdbadfc..4cc99912c6 100644 --- a/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D143Gaius.cs +++ b/BossMod/Modules/RealmReborn/Dungeon/D14Praetorium/D143Gaius.cs @@ -74,10 +74,14 @@ public override void AddGlobalHints(GlobalHints hints) hints.Add($"Enrage in {(_enrage - WorldState.CurrentTime).TotalSeconds:f1}s"); } - public override void OnEventCast(Actor caster, ActorCastEvent spell) + public override void Update() { - if ((AID)spell.Action.ID == AID.AddPhaseStart) - _enrage = WorldState.FutureTime(91); + var primary = Module.PrimaryActor.IsTargetable; + var enrage = _enrage == default; + if (enrage && !primary) + _enrage = WorldState.FutureTime(92.4f); + else if (!enrage && primary) + _enrage = default; } } @@ -104,6 +108,7 @@ public D143GaiusStates(BossModule module) : base(module) { protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.PhantomGaiusAdd).Concat([PrimaryActor])); + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(OID.PhantomGaiusAdd)); } } diff --git a/BossMod/Modules/Shadowbringers/FATE/Archaeotania.cs b/BossMod/Modules/Shadowbringers/FATE/Archaeotania.cs index 7e364bccc6..0c294f566e 100644 --- a/BossMod/Modules/Shadowbringers/FATE/Archaeotania.cs +++ b/BossMod/Modules/Shadowbringers/FATE/Archaeotania.cs @@ -117,4 +117,7 @@ public ArchaeotaniaStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.Fate, GroupID = 1432, NameID = 8234)] -public class Archaeotania(WorldState ws, Actor primary) : BossModule(ws, primary, new(279, 249), new ArenaBoundsSquare(29)); +public class Archaeotania(WorldState ws, Actor primary) : BossModule(ws, primary, new(279, 249), new ArenaBoundsSquare(29)) +{ + protected override bool CheckPull() => base.CheckPull() && (Center - Raid.Player()!.Position).LengthSq() < 900; +} diff --git a/BossMod/Modules/Shadowbringers/Quest/MSQ/TheGreatShipVylbrand.cs b/BossMod/Modules/Shadowbringers/Quest/MSQ/TheGreatShipVylbrand.cs index 5661b74600..6ad0651279 100644 --- a/BossMod/Modules/Shadowbringers/Quest/MSQ/TheGreatShipVylbrand.cs +++ b/BossMod/Modules/Shadowbringers/Quest/MSQ/TheGreatShipVylbrand.cs @@ -61,6 +61,64 @@ class TenTrolleyTorque(BossModule module) : Components.SimpleAOEs(module, Action class TenTrolleyWallop(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.TenTrolleyWallop), new AOEShapeCone(40, 30.Degrees())); class SelfDestruct2(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.SelfDestruct2), 10); +class Breakthrough(BossModule module) : Components.GenericAOEs(module) +{ + private readonly List _aoes = new(2); + private const string Hint = "Share damage inside wildcharge!"; + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Breakthrough) + { + var dir = spell.LocXZ - caster.Position; + _aoes.Add(new(new AOEShapeRect(dir.Length(), 4), caster.Position, Angle.FromDirection(dir), Module.CastFinishAt(spell), Colors.SafeFromAOE)); + } + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if (_aoes.Count != 0 && (AID)spell.Action.ID == AID.Breakthrough) + _aoes.RemoveAt(0); + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + var count = _aoes.Count; + if (count != 0) + { + var forbidden = new Func[count]; + for (var i = 0; i < count; ++i) + { + var aoe = _aoes[i]; + if (aoe.Shape is AOEShapeRect shape) + forbidden[i] = (shape with { InvertForbiddenZone = true }).Distance(aoe.Origin, aoe.Rotation); + } + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), _aoes[0].Activation); + } + } + + public override void AddHints(int slot, Actor actor, TextHints hints) + { + if (_aoes.Count == 0) + return; + + var shouldAddHint = true; + foreach (var c in ActiveAOEs(slot, actor)) + { + if (c.Check(actor.Position)) + { + shouldAddHint = false; + break; + } + } + if (shouldAddHint) + hints.Add(Hint); + else + hints.Add(Hint, false); + } +} + class Bulldoze(BossModule module) : Components.GenericAOEs(module) { private readonly List _aoes = new(4); @@ -123,7 +181,7 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) } } -class BombTether(BossModule module) : Components.InterceptTetherAOE(module, ActionID.MakeSpell(AID.SelfDestruct1), (uint)TetherID.BombTether, 6) +class BombTether(BossModule module) : Components.InterceptTetherAOE(module, ActionID.MakeSpell(AID.SelfDestruct1), (uint)TetherID.BombTether, 6, [(uint)OID.Alphinaud]) { public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { @@ -159,6 +217,7 @@ public class SecondOrderRocksplitterStates : StateMachineBuilder public SecondOrderRocksplitterStates(BossModule module) : base(module) { TrivialPhase() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() diff --git a/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P1GuidanceSystem.cs b/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P1GuidanceSystem.cs index abec4011ef..865cc54ca9 100644 --- a/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P1GuidanceSystem.cs +++ b/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P1GuidanceSystem.cs @@ -4,13 +4,14 @@ namespace BossMod.Shadowbringers.Quest.SorrowOfWerlyt.SleepNowInSapphire.P1Guida public enum OID : uint { - Boss = 0x2DFF, - Helper = 0x233C, + Boss = 0x2DFF, // R4.025 + Helper = 0x233C } public enum AID : uint { - AerialBombardment = 21492, // 233C->location, 2.5s cast, range 12 circle + AerialBombardmentVisual = 21491, // Boss->self, 3.0s cast, single-target + AerialBombardment = 21492, // Helper->location, 2.5s cast, range 12 circle } class AerialBombardment(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.AerialBombardment), 12); @@ -28,7 +29,7 @@ public GuidanceSystemStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.Quest, GroupID = 69431, NameID = 9461)] -public class GuidanceSystem(WorldState ws, Actor primary) : BossModule(ws, primary, new(-15, 610), new ArenaBoundsSquare(60)) +public class GuidanceSystem(WorldState ws, Actor primary) : SleepNowInSapphireSharedBounds(ws, primary) { protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { @@ -36,3 +37,8 @@ protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRoles hints.ActionsToExecute.Push(ActionID.MakeSpell(Roleplay.AID.PyreticBooster), actor, ActionQueue.Priority.Medium); } } + +public abstract class SleepNowInSapphireSharedBounds(WorldState ws, Actor primary) : BossModule(ws, primary, new(-15, 610), arena) +{ + private static readonly ArenaBoundsSquare arena = new(59.5f); +} diff --git a/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P2SapphireWeapon.cs b/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P2SapphireWeapon.cs index 2f710aa131..862126968b 100644 --- a/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P2SapphireWeapon.cs +++ b/BossMod/Modules/Shadowbringers/Quest/TheSorrowOfWerlyt/SleepNowInSapphire/P2SapphireWeapon.cs @@ -4,67 +4,112 @@ namespace BossMod.Shadowbringers.Quest.SorrowOfWerlyt.SleepNowInSapphire.P2Sapph public enum OID : uint { - Boss = 0x2DFA, - Helper = 0x233C, + Boss = 0x2DFA, // R19.400, x1 + RegulasImage = 0x2DFB, // R0.75 + MagitekTurret = 0x2DFC, // R1.65 + CeruleumServant = 0x2DFD, // R6.25 + Helper = 0x233C } public enum AID : uint { + AutoAttack = 6499, // Boss->player, no cast, single-target + Teleport = 20333, // MagitekTurret->location, no cast, single-target, if turret sucessfully self destructs, it teleports to player + + Activate = 20335, // Boss->self, 3.0s cast, single-target, spawns Regula's Images + SimultaneousActivation = 21395, // Boss->self, 3.0s cast, single-target TailSwing = 20326, // Boss->self, 4.0s cast, range 46 circle - OptimizedJudgment = 20325, // Boss->self, 4.0s cast, range 21-60 donut + OptimizedJudgment = 20325, // Boss->self, 4.0s cast, range 22-60 donut MagitekSpread = 20336, // RegulasImage->self, 5.0s cast, range 43 240-degree cone + RegulasVisual = 20337, // RegulasImage->self, no cast, single-target + SideraysVisual = 20328, // Boss->self, 8.0s cast, single-target SideraysRight = 20329, // Helper->self, 8.0s cast, range 128 90-degree cone SideraysLeft = 21021, // Helper->self, 8.0s cast, range 128 90-degree cone SapphireRay = 20327, // Boss->self, 8.0s cast, range 120 width 40 rect - MagitekRay = 20332, // 2DFC->self, 3.0s cast, range 100 width 6 rect - ServantRoar = 20339, // 2DFD->self, 2.5s cast, range 100 width 8 rect + Turret = 20331, // Boss->self, 3.0s cast, single-target + TurretVisual = 21465, // MagitekTurret->self, no cast, single-target + MagitekRay = 20332, // MagitekTurret->self, 3.0s cast, range 100 width 6 rect + ServantRoar = 20339, // CeruleumServant->self, 2.5s cast, range 100 width 8 rect + OptimizedUltima = 20342, // Boss->self, 6.0s cast, range 100 circle, raidwide + SwiftbreachVisual = 20330, // Boss->self, 4.5s cast, single-target + Swiftbreach = 21418, // Helper->self, 4.5s cast, range 120 width 120 rect, raidwide + PlasmaShot = 20340, // Boss->player, 6.0s cast, single-target, tankbuster + PlasmaCannon = 20341, // Boss->location, 5.5s cast, range 40 circle + SelfDestructVisual = 21489, // MagitekTurret->self, 30.0s cast, single-target + SelfDestruct = 20334, // MagitekTurret->self, no cast, range 50 circle, enrage, removes about 80% of total hp + FloodRay = 20338 // Boss->self, 115.0s cast, range 120 width 120 rect, enrage, duty fail } public enum SID : uint { - Invincibility = 775, // none->Boss, extra=0x0 + Invincibility = 775 // none->Boss, extra=0x0 } class MagitekRay(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.MagitekRay), new AOEShapeRect(100, 3)); class ServantRoar(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.ServantRoar), new AOEShapeRect(100, 4)); -class TailSwing(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.TailSwing), new AOEShapeCircle(46)); -class OptimizedJudgment(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.OptimizedJudgment), new AOEShapeDonut(21, 60)); class MagitekSpread(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.MagitekSpread), new AOEShapeCone(43, 120.Degrees())); + +class TailSwing(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.TailSwing), 46) +{ + private readonly MagitekSpread _aoe = module.FindComponent()!; + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + return Casters.Count != 0 && _aoe.Casters.Count == 0 ? [Casters[0]] : []; + } +} + +class OptimizedJudgment(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.OptimizedJudgment), new AOEShapeDonut(22, 60)); class SapphireRay(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.SapphireRay), new AOEShapeRect(120, 20)); abstract class Siderays(BossModule module, AID aid) : Components.SimpleAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCone(128, 45.Degrees())); class SideraysLeft(BossModule module) : Siderays(module, AID.SideraysLeft); class SideraysRight(BossModule module) : Siderays(module, AID.SideraysRight); +class FloodRay(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.FloodRay), "Enrage", true); +class SelfDestruct(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.SelfDestructVisual), "Turrets are enraging!", true); +class OptimizedUltima(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.OptimizedUltima)); +class Swiftbreach(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.Swiftbreach)); +class PlasmaShot(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.PlasmaShot)); +class PlasmaCannon(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.PlasmaCannon), 40); + class TheSapphireWeaponStates : StateMachineBuilder { public TheSapphireWeaponStates(BossModule module) : base(module) { TrivialPhase() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); } } [ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.Quest, GroupID = 69431, NameID = 9458)] -public class TheSapphireWeapon(WorldState ws, Actor primary) : BossModule(ws, primary, new(-15, 610), new ArenaBoundsSquare(60)) +public class TheSapphireWeapon(WorldState ws, Actor primary) : SleepNowInSapphireSharedBounds(ws, primary) { - protected override void DrawEnemies(int pcSlot, Actor pc) => Arena.Actors(WorldState.Actors.Where(x => !x.IsAlly)); + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies([(uint)OID.MagitekTurret, (uint)OID.CeruleumServant])); + } protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { for (var i = 0; i < hints.PotentialTargets.Count; ++i) { var h = hints.PotentialTargets[i]; - h.Priority = h.Actor.FindStatus(SID.Invincibility) == null ? 1 : 0; + h.Priority = h.Actor.FindStatus(SID.Invincibility) == null ? 1 : AIHints.Enemy.PriorityInvincible; } } } - diff --git a/BossMod/Modules/Stormblood/Dungeon/D15GhimlytDark/D150ScholaMarkIIColossus.cs b/BossMod/Modules/Stormblood/Dungeon/D15GhimlytDark/D150ScholaMarkIIColossus.cs index 29ff49a949..5773af4cc0 100644 --- a/BossMod/Modules/Stormblood/Dungeon/D15GhimlytDark/D150ScholaMarkIIColossus.cs +++ b/BossMod/Modules/Stormblood/Dungeon/D15GhimlytDark/D150ScholaMarkIIColossus.cs @@ -29,8 +29,8 @@ class SelfDetonate(BossModule module) : Components.CastHint(module, ActionID.Mak class UnbreakableCermetBlade(BossModule module) : Components.GenericAOEs(module) { private AOEInstance? _aoe; - private const string RiskHint = "Go under shield!"; - private const string StayHint = "Wait under shield!"; + private const string Hint = "Go under shield!"; + private static readonly AOEShapeCircle circle = new(4.5f, true); public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); @@ -47,10 +47,20 @@ public override void AddHints(int slot, Actor actor, TextHints hints) { if (_aoe == null) return; - if (ActiveAOEs(slot, actor).Any(c => !c.Check(actor.Position))) - hints.Add(RiskHint); + + var shouldAddHint = false; + foreach (var c in ActiveAOEs(slot, actor)) + { + if (!c.Check(actor.Position)) + { + shouldAddHint = true; + break; + } + } + if (shouldAddHint) + hints.Add(Hint); else - hints.Add(StayHint, false); + hints.Add(Hint, false); } } diff --git a/BossMod/QuestBattle/Shadowbringers/SideQuests/SleepNowInSapphire.cs b/BossMod/QuestBattle/Shadowbringers/SideQuests/SleepNowInSapphire.cs index c03412c84c..53f50af8b7 100644 --- a/BossMod/QuestBattle/Shadowbringers/SideQuests/SleepNowInSapphire.cs +++ b/BossMod/QuestBattle/Shadowbringers/SideQuests/SleepNowInSapphire.cs @@ -1,6 +1,4 @@ -using BossMod.Autorotation; - -namespace BossMod.QuestBattle.Shadowbringers.SideQuests; +namespace BossMod.QuestBattle.Shadowbringers.SideQuests; class SapphireWeapon(WorldState ws) : UnmanagedRotation(ws, 40) { @@ -96,9 +94,6 @@ public override List DefineObjectives(WorldState ws) => [ public override void AddQuestAIHints(Actor player, AIHints hints) { - hints.PathfindMapBounds = new ArenaBoundsSquare(60, default, 1); - hints.PathfindMapCenter = new WPos(-15, 610); - _weapon.Execute(player, hints); } }