From 509fc8c78c78fbf6e9f241f32c5c380678feb48e Mon Sep 17 00:00:00 2001 From: CarnifexOptimus <156172553+CarnifexOptimus@users.noreply.github.com> Date: Sun, 19 Jan 2025 21:36:21 +0100 Subject: [PATCH] BA Absolute Virtue module finished --- BossMod/Components/PersistentVoidzone.cs | 27 +- BossMod/Components/StackSpread.cs | 16 +- BossMod/Components/ThinIce.cs | 13 +- BossMod/Components/Towers.cs | 320 +++++++++++++++--- BossMod/Data/ActorState.cs | 20 +- .../Alliance/A12Fafnir/HurricaneWing.cs | 14 +- .../Alliance/A14ShadowLord/DarkNebula.cs | 13 +- .../Dungeon/D02WorqorZormor/D022Kahderyor.cs | 2 +- .../Dungeon/D02WorqorZormor/D023Gurfurlur.cs | 6 +- .../D03SkydeepCenote/D031FeatherRay.cs | 14 +- .../D07TenderValley/D071Barreltender.cs | 2 +- .../Dawntrail/Hunt/RankA/UrnaVariabilis.cs | 2 +- .../TheProtectorAndTheDestroyer/E2Gwyddrud.cs | 14 +- .../MSQ/TheWarmthOfTheFamily/Tturuhhetso.cs | 2 +- .../Raid/M02NHoneyBLovely/M02NHoneyBLovely.cs | 2 +- .../Raid/M02NHoneyBLovely/Sweethearts.cs | 13 +- .../T03QueenEternal/DownburstPowerfulGust.cs | 2 +- .../EurekaOrthos/DD40TwintaniasClone.cs | 2 +- .../Dungeon/D02TowerOfBabil/D021Barnabas.cs | 2 +- .../Dungeon/D03Vanaspati/D032Wrecker.cs | 6 +- .../Dungeon/D03Vanaspati/D033Svarbhanu.cs | 2 +- .../Dungeon/D05Aitiascope/D052Rhitahtyn.cs | 4 +- .../Dungeon/D06DeadEnds/D062Peacekeeper.cs | 14 +- .../D13LunarSubterrane/D132DamcyanAntlion.cs | 8 +- .../Extreme/Ex7Zeromus/VisceralWhirl.cs | 2 +- .../GymnasiouMeganereis.cs | 4 +- .../V02MR/V021Yozakura/V021Yozakura.cs | 4 +- .../Variant/V02MR/V022Moko/Spiritflames.cs | 2 +- .../Variant/V02MR/V022Moko/YamaKagura.cs | 2 +- .../Heavensward/Dungeon/D03Aery/D031Rangda.cs | 4 +- .../Dungeon/D13SohrKhai/D132Poqhiraj.cs | 2 +- .../RealmReborn/Raid/T01Caduceus/Platforms.cs | 2 +- .../RealmReborn/Raid/T05Twintania/Phase5.cs | 10 +- .../T04PortaDecumana/T04PortaDecumana2.cs | 2 +- .../Dungeon/D03QitanaRavel/D033Eros.cs | 4 +- .../D05MtGulg/D055ForgivenObscenity.cs | 2 +- .../Dungeon/D07Twinning/D071AlphaZaghnal.cs | 4 +- .../D08AkadaemiaAnyder/D083Quetzalcoatl.cs | 2 +- .../Dungeon/D09GrandCosmos/D092LeannanSith.cs | 2 +- .../Dungeon/D09GrandCosmos/D093Lugus.cs | 4 +- .../D10AnamnesisAnyder/D103RukshsDheem.cs | 16 +- .../D112SpectralNecromancer.cs | 4 +- .../D113SpectralBerserker.cs | 2 +- .../Dungeon/D12MatoyasRelict/D121Mudman.cs | 2 +- .../Dungeon/D13Paglthan/D131Amhuluk.cs | 2 +- .../D13Paglthan/D132MagitekFortress.cs | 2 +- .../CriticalEngagement/CE14VigilForLost.cs | 2 +- .../DaenOseTheAvariciousUltros.cs | 4 +- .../D05CastrumAbania/D051MagnaRoader.cs | 2 +- .../BA3AbsoluteVirtue/BA3AbsoluteVirtue.cs | 3 +- .../BA3AbsoluteVirtueEnums.cs | 2 +- .../BA3AbsoluteVirtueStates.cs | 121 ++++++- .../DarkBrightAuroraTowers.cs | 168 +++++++++ .../BA3AbsoluteVirtue/UmbralAstralAspect.cs | 278 --------------- .../UmbralAstralAspectAOEs.cs | 66 ++++ BossMod/Util/ShapeDistance.cs | 53 ++- 56 files changed, 773 insertions(+), 526 deletions(-) create mode 100644 BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/DarkBrightAuroraTowers.cs delete mode 100644 BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspect.cs create mode 100644 BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspectAOEs.cs diff --git a/BossMod/Components/PersistentVoidzone.cs b/BossMod/Components/PersistentVoidzone.cs index 3b4557c6da..79fe03f3b1 100644 --- a/BossMod/Components/PersistentVoidzone.cs +++ b/BossMod/Components/PersistentVoidzone.cs @@ -26,19 +26,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(); foreach (var s in Sources(Module)) forbidden.Add(Shape.Distance(s.Position, s.Rotation)); - hints.AddForbiddenZone(minDistanceFunc); - - float minDistanceFunc(WPos pos) - { - var minDistance = float.MaxValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance < minDistance) - minDistance = distance; - } - return minDistance; - } + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } } @@ -125,18 +113,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (shapes.Count == 0) return; - float distance(WPos p) - { - var minDistance = float.MaxValue; - for (var i = 0; i < shapes.Count; ++i) - { - var distance = shapes[i](p); - if (distance < minDistance) - minDistance = distance; - } - return Inverted ? -minDistance : minDistance; - } - hints.AddForbiddenZone(distance, InvertResolveAt); + hints.AddForbiddenZone(Inverted ? ShapeDistance.InvertedUnion(shapes) : ShapeDistance.Union(shapes), InvertResolveAt); } // TODO: reconsider - draw foreground circles instead? diff --git a/BossMod/Components/StackSpread.cs b/BossMod/Components/StackSpread.cs index 6c5bf1d6c5..144e1fb190 100644 --- a/BossMod/Components/StackSpread.cs +++ b/BossMod/Components/StackSpread.cs @@ -185,7 +185,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme foreach (var stackWith in ActiveStacks.Where(s => s.Target == actor)) forbidden.Add(ShapeDistance.InvertedCircle(Raid.WithoutSlot().FirstOrDefault(x => !x.IsDead && !IsSpreadTarget(x) && !IsStackTarget(x))!.Position, actorStack.Radius / 3)); if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), actorStack.Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), actorStack.Activation); } } else if (!IsSpreadTarget(actor) && !IsStackTarget(actor)) @@ -195,7 +195,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme || !x.IsInside(actor) && x.InsufficientAmountInside(Module)))) forbidden.Add(ShapeDistance.InvertedCircle(s.Target.Position, s.Radius - 0.25f)); if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), ActiveStacks.FirstOrDefault().Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), ActiveStacks.FirstOrDefault().Activation); } if (RaidwideOnResolve) @@ -493,10 +493,10 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (isBaitNotTarget && isBaitTarget || forbiddenActors) foreach (var b in ActiveBaits.Where(x => x.Target != actor)) forbidden.Add(ShapeDistance.Rect(b.Source.Position, b.Rotation, Range, 0, 2 * HalfWidth)); - if (forbiddenInverted.Count > 0) - hints.AddForbiddenZone(p => forbiddenInverted.Max(f => f(p)), ActiveBaits.FirstOrDefault().Activation); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), ActiveBaits.FirstOrDefault().Activation); + if (forbiddenInverted.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbiddenInverted), ActiveBaits.FirstOrDefault().Activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), ActiveBaits.FirstOrDefault().Activation); } public override void AddHints(int slot, Actor actor, TextHints hints) @@ -571,8 +571,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(); foreach (var c in Raid.WithoutSlot().Where(x => ActiveStacks.Any(y => y.Target == x)).Exclude(actor)) forbidden.Add(ShapeDistance.InvertedCircle(c.Position, Donut.InnerRadius * 0.25f)); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), ActiveStacks.FirstOrDefault().Activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), ActiveStacks.FirstOrDefault().Activation); } public override void DrawArenaBackground(int pcSlot, Actor pc) diff --git a/BossMod/Components/ThinIce.cs b/BossMod/Components/ThinIce.cs index fa7c5009b2..6c6b582094 100644 --- a/BossMod/Components/ThinIce.cs +++ b/BossMod/Components/ThinIce.cs @@ -52,18 +52,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme ShapeDistance.InvertedDonut(pos, ddistance, ddistance + 0.5f), ShapeDistance.InvertedRect(pos, offset, 0.5f, 0.5f, 0.5f) }; - float maxDistanceFunc(WPos pos) - { - var minDistance = float.MinValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance > minDistance) - minDistance = distance; - } - return minDistance; - } - hints.AddForbiddenZone(maxDistanceFunc, DateTime.MaxValue); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), DateTime.MaxValue); } } } diff --git a/BossMod/Components/Towers.cs b/BossMod/Components/Towers.cs index f66c1c0311..6eb2328656 100644 --- a/BossMod/Components/Towers.cs +++ b/BossMod/Components/Towers.cs @@ -197,35 +197,13 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme } var ficount = forbiddenInverted.Count; var fcount = forbidden.Count; - if (forbidden.Count == 0 || inTower || missingSoakers && ficount != 0) + if (fcount == 0 || inTower || missingSoakers && ficount != 0) { - float distance(WPos p) - { - var maxDist = float.MinValue; - for (var i = 0; i < ficount; ++i) - { - var distance = forbiddenInverted[i](p); - if (distance > maxDist) - maxDist = distance; - } - return maxDist; - } - hints.AddForbiddenZone(distance, Towers[0].Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbiddenInverted), Towers[0].Activation); } else if (fcount != 0 && !inTower) { - float distance(WPos p) - { - var minDist = float.MaxValue; - for (var i = 0; i < fcount; ++i) - { - var distance = forbidden[i](p); - if (distance < minDist) - minDist = distance; - } - return minDist; - } - hints.AddForbiddenZone(distance, Towers[0].Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), Towers[0].Activation); } } else @@ -238,18 +216,7 @@ float distance(WPos p) var fcount = forbidden.Count; if (fcount != 0) { - float distance(WPos p) - { - var minDist = float.MaxValue; - for (var i = 0; i < fcount; ++i) - { - var distance = forbidden[i](p); - if (distance < minDist) - minDist = distance; - } - return minDist; - } - hints.AddForbiddenZone(distance, Towers[0].Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), Towers[0].Activation); } } for (var i = 0; i < count; ++i) @@ -286,17 +253,288 @@ public class CastTowers(BossModule module, ActionID aid, float radius, int minSo public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if (spell.Action == WatchedAction) - Towers.Add(new(DeterminePosition(caster, spell), Radius, MinSoakers, MaxSoakers, activation: Module.CastFinishAt(spell))); + Towers.Add(new(spell.LocXZ, Radius, MinSoakers, MaxSoakers, activation: Module.CastFinishAt(spell))); } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { if (spell.Action == WatchedAction) { - var pos = DeterminePosition(caster, spell); - Towers.RemoveAll(t => t.Position.AlmostEqual(pos, 1)); + for (var i = 0; i < Towers.Count; ++i) + { + var tower = Towers[i]; + if (tower.Position == spell.LocXZ) + { + Towers.Remove(tower); + break; + } + } + } + } +} + +// for tower mechanics in open world since likely not everyone is in your party +public class GenericTowersOpenWorld(BossModule module, ActionID aid = default, bool prioritizeInsufficient = false) : CastCounter(module, aid) +{ + public struct Tower(WPos position, float radius, int minSoakers = 1, int maxSoakers = 1, HashSet? allowedSoakers = null, DateTime activation = default) + { + public WPos Position = position; + public float Radius = radius; + public int MinSoakers = minSoakers; + public int MaxSoakers = maxSoakers; + public HashSet? AllowedSoakers = allowedSoakers; + public DateTime Activation = activation; + + public readonly bool IsInside(WPos pos) => pos.InCircle(Position, Radius); + public readonly bool IsInside(Actor actor) => IsInside(actor.Position); + public static HashSet Soakers(BossModule module) + { + HashSet actors = new(module.WorldState.Actors.Actors.Values.Count); + foreach (var a in module.WorldState.Actors.Actors.Values) + if (a.OID == 0) + actors.Add(a); + return actors; + } + + public int NumInside(BossModule module) + { + var count = 0; + var allowedSoakers = AllowedSoakers ??= Soakers(module); + foreach (var a in allowedSoakers) + { + if (a.Position.InCircle(Position, Radius)) + ++count; + } + return count; + } + + public bool CorrectAmountInside(BossModule module) => NumInside(module) is var count && count >= MinSoakers && count <= MaxSoakers; + public bool InsufficientAmountInside(BossModule module) => NumInside(module) is var count && count < MaxSoakers; + public bool TooManyInside(BossModule module) => NumInside(module) is var count && count > MaxSoakers; + } + + public readonly List Towers = []; + public readonly bool PrioritizeInsufficient = prioritizeInsufficient; // give priority to towers with more than 0 but less than min soakers + + // default tower styling + public static void DrawTower(MiniArena arena, WPos pos, float radius, bool safe) + { + arena.AddCircle(pos, radius, safe ? Colors.Safe : 0, 2); + } + + public override void AddHints(int slot, Actor actor, TextHints hints) + { + var count = Towers.Count; + if (count == 0) + return; + var gtfoFromTower = false; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + var allowedSoakers = t.AllowedSoakers ??= Tower.Soakers(Module); + if (!allowedSoakers.Contains(actor) && t.IsInside(actor)) + { + gtfoFromTower = true; + break; + } + } + + if (gtfoFromTower) + { + hints.Add("GTFO from tower!"); + } + else // Find index of a tower that is not forbidden and the actor is inside + { + var soakedIndex = -1; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + var allowedSoakers = t.AllowedSoakers ??= Tower.Soakers(Module); + if (allowedSoakers.Contains(actor) && t.IsInside(actor)) + { + soakedIndex = i; + break; + } + } + + if (soakedIndex >= 0) // If a suitable tower is found + { + var count2 = Towers[soakedIndex].NumInside(Module); + if (count2 < Towers[soakedIndex].MinSoakers) + hints.Add("Too few soakers in the tower!"); + else if (count2 > Towers[soakedIndex].MaxSoakers) + hints.Add("Too many soakers in the tower!"); + else + hints.Add("Soak the tower!", false); + } + else // Check if any tower has insufficient soakers + { + var insufficientSoakers = false; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + var allowedSoakers = t.AllowedSoakers ??= Tower.Soakers(Module); + if (allowedSoakers.Contains(actor) && t.InsufficientAmountInside(Module)) + { + insufficientSoakers = true; + break; + } + } + if (insufficientSoakers) + hints.Add("Soak the tower!"); + } + } + } + + public override void DrawArenaForeground(int pcSlot, Actor pc) + { + var count = Towers.Count; + if (count == 0) + return; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + var allowedSoakers = t.AllowedSoakers ??= Tower.Soakers(Module); + DrawTower(Arena, t.Position, t.Radius, allowedSoakers.Contains(pc) && !t.IsInside(pc) && t.NumInside(Module) < t.MaxSoakers || t.IsInside(pc) && allowedSoakers.Contains(pc) && t.NumInside(Module) <= t.MaxSoakers); + } + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + var count = Towers.Count; + if (count == 0) + return; + var forbiddenInverted = new List>(count); + var forbidden = new List>(count); + + var hasForbiddenSoakers = false; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + var allowedSoakers = t.AllowedSoakers ??= Tower.Soakers(Module); + if (!allowedSoakers.Contains(actor)) + { + hasForbiddenSoakers = true; + break; + } + } + if (!hasForbiddenSoakers) + { + if (PrioritizeInsufficient) + { + List insufficientTowers = new(count); + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + if (t.InsufficientAmountInside(Module) && t.NumInside(Module) != 0) + insufficientTowers.Add(t); + } + Tower? mostRelevantTower = null; + + for (var i = 0; i < insufficientTowers.Count; ++i) + { + var t = insufficientTowers[i]; + if (mostRelevantTower == null || t.NumInside(Module) > mostRelevantTower.Value.NumInside(Module) || t.NumInside(Module) == mostRelevantTower.Value.NumInside(Module) && + (t.Position - actor.Position).LengthSq() < (mostRelevantTower.Value.Position - actor.Position).LengthSq()) + mostRelevantTower = t; + } + if (mostRelevantTower.HasValue) + forbiddenInverted.Add(ShapeDistance.InvertedCircle(mostRelevantTower.Value.Position, mostRelevantTower.Value.Radius)); + } + var inTower = false; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + if (t.IsInside(actor) && t.CorrectAmountInside(Module)) + { + inTower = true; + break; + } + } + + var missingSoakers = !inTower; + if (missingSoakers) + { + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + if (t.InsufficientAmountInside(Module)) + { + missingSoakers = true; + break; + } + } + } + if (forbiddenInverted.Count == 0) + { + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + if (t.InsufficientAmountInside(Module) || t.IsInside(actor) && t.CorrectAmountInside(Module)) + forbiddenInverted.Add(ShapeDistance.InvertedCircle(t.Position, t.Radius)); + } + + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + if (t.TooManyInside(Module) || !t.IsInside(actor) && t.CorrectAmountInside(Module)) + { + forbidden.Add(ShapeDistance.Circle(t.Position, t.Radius)); + } + } + } + var ficount = forbiddenInverted.Count; + var fcount = forbidden.Count; + if (fcount == 0 || inTower || missingSoakers && ficount != 0) + { + hints.AddForbiddenZone(ShapeDistance.Intersection(forbiddenInverted), Towers[0].Activation); + } + else if (fcount != 0 && !inTower) + { + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), Towers[0].Activation); + } + } + else + { + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + forbidden.Add(ShapeDistance.Circle(t.Position, t.Radius)); + } + + if (forbidden.Count != 0) + { + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), Towers[0].Activation); + } } } +} + +public class CastTowersOpenWorld(BossModule module, ActionID aid, float radius, int minSoakers = 1, int maxSoakers = 1) : GenericTowersOpenWorld(module, aid) +{ + public readonly float Radius = radius; + public readonly int MinSoakers = minSoakers; + public readonly int MaxSoakers = maxSoakers; + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + if (spell.Action == WatchedAction) + Towers.Add(new(spell.LocXZ, Radius, MinSoakers, MaxSoakers, activation: Module.CastFinishAt(spell))); + } - private WPos DeterminePosition(Actor caster, ActorCastInfo spell) => spell.TargetID == caster.InstanceID ? caster.Position : WorldState.Actors.Find(spell.TargetID)?.Position ?? spell.LocXZ; + public override void OnCastFinished(Actor caster, ActorCastInfo spell) + { + if (spell.Action == WatchedAction) + { + for (var i = 0; i < Towers.Count; ++i) + { + var tower = Towers[i]; + if (tower.Position == spell.LocXZ) + { + Towers.Remove(tower); + break; + } + } + } + } } diff --git a/BossMod/Data/ActorState.cs b/BossMod/Data/ActorState.cs index a6bff8594b..9e1abff485 100644 --- a/BossMod/Data/ActorState.cs +++ b/BossMod/Data/ActorState.cs @@ -4,12 +4,12 @@ // TODO: consider indexing by spawnindex?.. public sealed class ActorState : IEnumerable { - private readonly Dictionary _actors = []; + public readonly Dictionary Actors = []; - public IEnumerator GetEnumerator() => _actors.Values.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _actors.Values.GetEnumerator(); + public IEnumerator GetEnumerator() => Actors.Values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Actors.Values.GetEnumerator(); - public Actor? Find(ulong instanceID) => instanceID is not 0 and not 0xE0000000 ? _actors.GetValueOrDefault(instanceID) : null; + public Actor? Find(ulong instanceID) => instanceID is not 0 and not 0xE0000000 ? Actors.GetValueOrDefault(instanceID) : null; // all actor-related operations have instance ID to which they are applied // in addition to worldstate's modification event, extra event with actor pointer is dispatched for all actor events @@ -18,16 +18,16 @@ public abstract record class Operation(ulong InstanceID) : WorldState.Operation protected abstract void ExecActor(WorldState ws, Actor actor); protected override void Exec(WorldState ws) { - if (ws.Actors._actors.TryGetValue(InstanceID, out var actor)) + if (ws.Actors.Actors.TryGetValue(InstanceID, out var actor)) ExecActor(ws, actor); } } public List CompareToInitial() { - List ops = new(_actors.Count * 2); + List ops = new(Actors.Count * 2); - foreach (var act in _actors.Values) + foreach (var act in Actors.Values) { var instanceID = act.InstanceID; ops.Add(new OpCreate(instanceID, act.OID, act.SpawnIndex, act.Name, act.NameID, act.Type, act.Class, act.Level, act.PosRot, act.HitboxRadius, act.HPMP, act.IsTargetable, act.IsAlly, act.OwnerID, act.FateID)); @@ -59,7 +59,7 @@ public List CompareToInitial() public void Tick(float dt) { - foreach (var act in _actors.Values) + foreach (var act in Actors.Values) { act.PrevPosRot = act.PosRot; if (act.CastInfo != null) @@ -76,7 +76,7 @@ public sealed record class OpCreate(ulong InstanceID, uint OID, int SpawnIndex, protected override void ExecActor(WorldState ws, Actor actor) { } protected override void Exec(WorldState ws) { - var actor = ws.Actors._actors[InstanceID] = new Actor(InstanceID, OID, SpawnIndex, Name, NameID, Type, Class, Level, PosRot, HitboxRadius, HPMP, IsTargetable, IsAlly, OwnerID, FateID); + var actor = ws.Actors.Actors[InstanceID] = new Actor(InstanceID, OID, SpawnIndex, Name, NameID, Type, Class, Level, PosRot, HitboxRadius, HPMP, IsTargetable, IsAlly, OwnerID, FateID); ws.Actors.Added.Fire(actor); } public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("ACT+"u8) @@ -131,7 +131,7 @@ protected override void ExecActor(WorldState ws, Actor actor) } } ws.Actors.Removed.Fire(actor); - ws.Actors._actors.Remove(InstanceID); + ws.Actors.Actors.Remove(InstanceID); } public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("ACT-"u8).Emit(InstanceID, "X8"); } diff --git a/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/HurricaneWing.cs b/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/HurricaneWing.cs index 73c6bd6605..d11fff72f3 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/HurricaneWing.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/HurricaneWing.cs @@ -141,18 +141,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var w = _smallWhirldwinds[i]; forbidden.Add(ShapeDistance.Capsule(w.Position, !moving ? w.Rotation + a180 : w.Rotation, length, 5)); } - float minDistanceFunc(WPos pos) - { - var minDistance = float.MaxValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance < minDistance) - minDistance = distance; - } - return minDistance; - } - hints.AddForbiddenZone(minDistanceFunc, WorldState.FutureTime(1.1f)); + + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), WorldState.FutureTime(1.1f)); } } diff --git a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/DarkNebula.cs b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/DarkNebula.cs index 589f89c84e..088dabf1ab 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/DarkNebula.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/DarkNebula.cs @@ -72,18 +72,7 @@ static Func CreateForbiddenZone(int circleIndex, WDir dir) var circleIndex = rotationMatch ? mapping.CircleIndices[0] : mapping.CircleIndices[1]; forbidden.Add(CreateForbiddenZone(circleIndex, mapping.Directions)); } - float maxDistanceFunc(WPos pos) - { - var minDistance = float.MinValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance > minDistance) - minDistance = distance; - } - return minDistance; - } - hints.AddForbiddenZone(maxDistanceFunc, Sources(slot, actor).FirstOrDefault().Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), Sources(slot, actor).FirstOrDefault().Activation); } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs index 96a8b1a57d..9fbb318715 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs @@ -141,7 +141,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme foreach (var c in Raid.WithoutSlot(false, true, true).Exclude(actor).Where(x => comp.Any(c => c.Shape is AOEShapeDonut && !c.Check(x.Position) || c.Shape is AOEShapeCustom && c.Check(x.Position)))) forbidden.Add(ShapeDistance.InvertedCircle(c.Position, Donut.InnerRadius * 0.33f)); if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), ActiveStacks.FirstOrDefault().Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), ActiveStacks.FirstOrDefault().Activation); } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs index 0e826cd699..c376e99d5d 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs @@ -90,7 +90,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme } } if (orbs.Count != 0) - hints.AddForbiddenZone(p => orbs.Max(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Intersection(orbs)); } public override void DrawArenaForeground(int pcSlot, Actor pc) @@ -252,8 +252,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme } else forbidden.Add(ShapeDistance.InvertedCircle(sources.Origin, 8)); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), sources.Activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), sources.Activation); } } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs index 6d68063667..4006898abe 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs @@ -100,18 +100,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme forbidden.Add(ShapeDistance.Capsule(o.Position, o.Rotation, Length, Radius)); } forbidden.Add(ShapeDistance.Circle(Arena.Center, Module.PrimaryActor.HitboxRadius)); - float minDistance(WPos p) - { - var minValue = float.MaxValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var value = forbidden[i](p); - if (value < minValue) - minValue = value; - } - return minValue; - } - hints.AddForbiddenZone(minDistance); + + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D071Barreltender.cs b/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D071Barreltender.cs index 8bbe99511e..be3654ec78 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D071Barreltender.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D071Barreltender.cs @@ -156,7 +156,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme forbidden.Add(ShapeDistance.InvertedDonutSector(source.Origin, 4, 5, cactusSmall != default ? -a45 : a45, a5)); } if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), source.Activation); } public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || !Module.InBounds(pos); diff --git a/BossMod/Modules/Dawntrail/Hunt/RankA/UrnaVariabilis.cs b/BossMod/Modules/Dawntrail/Hunt/RankA/UrnaVariabilis.cs index ed96db6544..8ae743b81a 100644 --- a/BossMod/Modules/Dawntrail/Hunt/RankA/UrnaVariabilis.cs +++ b/BossMod/Modules/Dawntrail/Hunt/RankA/UrnaVariabilis.cs @@ -134,7 +134,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme else if (IsPull(actor, Shape.Donut, MagneticPole.Plus) || IsPull(actor, Shape.Donut, MagneticPole.Minus)) forbidden.Add(ShapeDistance.InvertedCircle(Module.PrimaryActor.Position, 18)); if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), activation); } } diff --git a/BossMod/Modules/Dawntrail/Quest/MSQ/TheProtectorAndTheDestroyer/E2Gwyddrud.cs b/BossMod/Modules/Dawntrail/Quest/MSQ/TheProtectorAndTheDestroyer/E2Gwyddrud.cs index 6773dc7911..668c5af5b2 100644 --- a/BossMod/Modules/Dawntrail/Quest/MSQ/TheProtectorAndTheDestroyer/E2Gwyddrud.cs +++ b/BossMod/Modules/Dawntrail/Quest/MSQ/TheProtectorAndTheDestroyer/E2Gwyddrud.cs @@ -116,19 +116,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { for (var i = 0; i < count; ++i) forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, Angle.FromDirection(aoes[i].Origin - Arena.Center), a25)); - float minDistanceFunc(WPos pos) - { - var minDistance = float.MaxValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance < minDistance) - minDistance = distance; - } - return minDistance; - } - - hints.AddForbiddenZone(minDistanceFunc, source.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Dawntrail/Quest/MSQ/TheWarmthOfTheFamily/Tturuhhetso.cs b/BossMod/Modules/Dawntrail/Quest/MSQ/TheWarmthOfTheFamily/Tturuhhetso.cs index 6270cf6877..629e3ab927 100644 --- a/BossMod/Modules/Dawntrail/Quest/MSQ/TheWarmthOfTheFamily/Tturuhhetso.cs +++ b/BossMod/Modules/Dawntrail/Quest/MSQ/TheWarmthOfTheFamily/Tturuhhetso.cs @@ -113,7 +113,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme foreach (var o in ActiveOrbs) orbs.Add(ShapeDistance.InvertedCircle(o.Position + 0.56f * o.Rotation.ToDirection(), 0.75f)); if (orbs.Count > 0) - hints.AddForbiddenZone(p => orbs.Max(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Intersection(orbs)); } public override void DrawArenaForeground(int pcSlot, Actor pc) diff --git a/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/M02NHoneyBLovely.cs b/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/M02NHoneyBLovely.cs index 3b9b0bcc74..8e3113d925 100644 --- a/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/M02NHoneyBLovely.cs +++ b/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/M02NHoneyBLovely.cs @@ -43,7 +43,7 @@ public override void Update() { if (Towers.Count == 0) return; - var forbidden = Raid.WithSlot(false, true, true).WhereActor(p => p.Statuses.Where(i => i.ID is ((uint)SID.HeadOverHeels) or ((uint)SID.HopelessDevotion)).Any()).Mask(); + var forbidden = Raid.WithSlot(false, true, true).WhereActor(p => p.Statuses.Any(i => i.ID is ((uint)SID.HeadOverHeels) or ((uint)SID.HopelessDevotion))).Mask(); foreach (ref var t in Towers.AsSpan()) t.ForbiddenSoakers = forbidden; } diff --git a/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs b/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs index cb08efc911..f0b8d58110 100644 --- a/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs +++ b/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs @@ -51,18 +51,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme } forbidden.Add(ShapeDistance.Circle(Arena.Center, Module.PrimaryActor.HitboxRadius)); - float minDistance(WPos p) - { - var minValue = float.MaxValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var value = forbidden[i](p); - if (value < minValue) - minValue = value; - } - return minValue; - } - hints.AddForbiddenZone(minDistance); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } } diff --git a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/DownburstPowerfulGust.cs b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/DownburstPowerfulGust.cs index 1a178b3208..16e3c7057c 100644 --- a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/DownburstPowerfulGust.cs +++ b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/DownburstPowerfulGust.cs @@ -30,7 +30,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(4); for (var i = 0; i < 4; ++i) forbidden.Add(ShapeDistance.InvertedCone(source.Origin, 5, source.Direction + Angle.AnglesCardinals[i], 10.Degrees())); - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/DD40TwintaniasClone.cs b/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/DD40TwintaniasClone.cs index 03e4ca7f2b..03e9ec983a 100644 --- a/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/DD40TwintaniasClone.cs +++ b/BossMod/Modules/Endwalker/DeepDungeon/EurekaOrthos/DD40TwintaniasClone.cs @@ -72,7 +72,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme for (var i = 0; i < component.Count; ++i) forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, Angle.FromDirection(component[i].Origin - Arena.Center), 20.Degrees())); if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), source.Activation); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D02TowerOfBabil/D021Barnabas.cs b/BossMod/Modules/Endwalker/Dungeon/D02TowerOfBabil/D021Barnabas.cs index 831c892f8a..0a962641c5 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D02TowerOfBabil/D021Barnabas.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D02TowerOfBabil/D021Barnabas.cs @@ -158,7 +158,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme else if (IsPull(actor, Shape.Rect, MagneticPole.Plus) || IsPull(actor, Shape.Rect, MagneticPole.Minus)) forbidden.Add(ShapeDistance.Rect(Arena.Center, rotation, 15, 15, 12)); if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), activation); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs index fdc862269e..a3978ba9c7 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs @@ -61,8 +61,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme foreach (var a in _aoes) forbiddenInverted.Add(ShapeDistance.InvertedCircle(a.Position, 2.5f)); var activation = Module.CastFinishAt(Module.FindComponent()!.Casters[0].CastInfo); - if (forbiddenInverted.Count > 0) - hints.AddForbiddenZone(p => forbiddenInverted.Select(f => f(p)).Max(), activation); + if (forbiddenInverted.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbiddenInverted), activation); } else base.AddAIHints(slot, actor, assignment, hints); @@ -92,7 +92,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme for (var i = 0; i < 6; ++i) if (Module.Enemies(OID.QueerBubble).Where(x => x.Position.AlmostEqual(WPos.RotateAroundOrigin(i * 60, Arena.Center, x.Position), 1) && Module.FindComponent()!._aoes.Contains(x)) != null) forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, i * a60, a10)); - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs index adc1a1843b..86f87a96ad 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs @@ -197,7 +197,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme else if (component!.Any(x => x.Origin.Z == -162) && component!.Any(x => x.Origin.Z == -172)) forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, a0, a90)); if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D05Aitiascope/D052Rhitahtyn.cs b/BossMod/Modules/Endwalker/Dungeon/D05Aitiascope/D052Rhitahtyn.cs index 963e7a2165..d4900bf673 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D05Aitiascope/D052Rhitahtyn.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D05Aitiascope/D052Rhitahtyn.cs @@ -85,8 +85,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (shape is Rectangle rectangle) forbidden.Add(ShapeDistance.InvertedCircle(rectangle.Center, 5)); } - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), activation); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D062Peacekeeper.cs b/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D062Peacekeeper.cs index dbdd9a1939..fa11cac48e 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D062Peacekeeper.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D062Peacekeeper.cs @@ -91,19 +91,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme forbidden.Add(ShapeDistance.Cone(Arena.Center, 16, Angle.FromDirection(component[i].Origin - Arena.Center), a36)); forbidden.Add(ShapeDistance.InvertedCircle(Arena.Center, 4)); - hints.AddForbiddenZone(minDistanceFunc, source.Activation); - - float minDistanceFunc(WPos pos) - { - var minDistance = float.MaxValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance < minDistance) - minDistance = distance; - } - return minDistance; - } + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D132DamcyanAntlion.cs b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D132DamcyanAntlion.cs index 822958ca1b..e890870811 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D132DamcyanAntlion.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D132DamcyanAntlion.cs @@ -185,10 +185,10 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme forbidden.Add(ShapeDistance.Rect(_aoes[i].Origin, _aoes[i].Rotation, rect.LengthFront, default, rect.HalfWidth)); } var activation = Module.FindComponent()!.Activation.AddSeconds(0.7f); - if (forbiddenInverted.Count > 0) - hints.AddForbiddenZone(p => forbiddenInverted.Max(f => f(p)), activation); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), activation); + if (forbiddenInverted.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbiddenInverted), activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), activation); } else base.AddAIHints(slot, actor, assignment, hints); diff --git a/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VisceralWhirl.cs b/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VisceralWhirl.cs index 42a0ebffe7..0a668e0f5b 100644 --- a/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VisceralWhirl.cs +++ b/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VisceralWhirl.cs @@ -72,7 +72,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var h = bubbles[i]; forbidden.Add(ShapeDistance.Capsule(h.Position, angle, 3, 2)); // merging all forbidden zones into one to make pathfinding less demanding } - hints.AddForbiddenZone(p => forbidden.Min(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } } diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs index 3035c1805b..ad6ec7b2c7 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs @@ -59,8 +59,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, Angle.FromDirection(c.Origin - Module.Center), 30.Degrees())); } - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Endwalker/Variant/V02MR/V021Yozakura/V021Yozakura.cs b/BossMod/Modules/Endwalker/Variant/V02MR/V021Yozakura/V021Yozakura.cs index c6583436a3..76d70623c4 100644 --- a/BossMod/Modules/Endwalker/Variant/V02MR/V021Yozakura/V021Yozakura.cs +++ b/BossMod/Modules/Endwalker/Variant/V02MR/V021Yozakura/V021Yozakura.cs @@ -19,8 +19,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (component != null && component.Count != 0) foreach (var c in component) forbidden.Add(ShapeDistance.Cone(source.Origin, 20, Angle.FromDirection(c.Origin - Arena.Center), 20.Degrees())); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } diff --git a/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/Spiritflames.cs b/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/Spiritflames.cs index 6d9fb225f3..16af0336df 100644 --- a/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/Spiritflames.cs +++ b/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/Spiritflames.cs @@ -32,6 +32,6 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(); foreach (var f in _flames) forbidden.Add(ShapeDistance.Capsule(f.Position, f.Rotation, Length, Radius)); - hints.AddForbiddenZone(p => forbidden.Min(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } } diff --git a/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/YamaKagura.cs b/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/YamaKagura.cs index 65e832a588..662880f45e 100644 --- a/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/YamaKagura.cs +++ b/BossMod/Modules/Endwalker/Variant/V02MR/V022Moko/YamaKagura.cs @@ -13,7 +13,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme foreach (var d in Sources(slot, actor)) forbidden.Add(ShapeDistance.Rect(d.Origin, d.Direction, length, Distance - length, 2.5f)); if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } diff --git a/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs b/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs index 3793516ab0..fd7404133d 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs @@ -71,8 +71,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(); foreach (var a in Module.Enemies(OID.BlackenedStatue)) forbidden.Add(ShapeDistance.InvertedCircle(a.Position, 4)); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), ActiveBaits.FirstOrDefault().Activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), ActiveBaits.FirstOrDefault().Activation); } public override void DrawArenaForeground(int pcSlot, Actor pc) diff --git a/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs b/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs index a5ae2ec79a..7cac1e294f 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs @@ -209,7 +209,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme forbidden.Add(ShapeDistance.Cone(bait.Source.Position, 100, bait.Source.AngleTo(a), Angle.Asin(8 / (a.Position - bait.Source.Position).Length()))); } if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), bait.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), bait.Activation); } } } diff --git a/BossMod/Modules/RealmReborn/Raid/T01Caduceus/Platforms.cs b/BossMod/Modules/RealmReborn/Raid/T01Caduceus/Platforms.cs index 44f80d78cd..5813b5c277 100644 --- a/BossMod/Modules/RealmReborn/Raid/T01Caduceus/Platforms.cs +++ b/BossMod/Modules/RealmReborn/Raid/T01Caduceus/Platforms.cs @@ -35,7 +35,7 @@ class Platforms(BossModule module) : BossComponent(module) new(0, -HexaPlatformSide) ]; - public static readonly Func[] PlatformShapes = [.. Enumerable.Range(0, HexaPlatformCenters.Length + 1).Select(i => ShapeDistance.ConvexPolygon([.. PlatformPoly(i)], true, Pathfinding.NavigationDecision.DefaultForbiddenZoneCushion))]; + public static readonly Func[] PlatformShapes = [.. Enumerable.Range(0, HexaPlatformCenters.Length + 1).Select(i => ShapeDistance.ConvexPolygon([.. PlatformPoly(i)], true))]; public static readonly Func[] HighEdgeShapes = [.. HighEdges.Select(e => HexaEdge(e.lower, e.upper)).Select(e => ShapeDistance.Rect(e.Item1, e.Item2, 0))]; public static readonly (WPos p, WDir d, float l)[] JumpEdgeSegments = [.. JumpEdges.Select(e => HexaEdge(e.lower, e.upper)).Select(e => (e.Item1, (e.Item2 - e.Item1).Normalized(), (e.Item2 - e.Item1).Length()))]; diff --git a/BossMod/Modules/RealmReborn/Raid/T05Twintania/Phase5.cs b/BossMod/Modules/RealmReborn/Raid/T05Twintania/Phase5.cs index 5a8a184ea8..d0f0a79189 100644 --- a/BossMod/Modules/RealmReborn/Raid/T05Twintania/Phase5.cs +++ b/BossMod/Modules/RealmReborn/Raid/T05Twintania/Phase5.cs @@ -83,8 +83,14 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme //if (!orbIntercepted) { forbidNeurolinks = false; - var forbidden = _hatch.Neurolinks.Exclude(neurolinkUnderBoss).Select(n => ShapeDistance.Circle(n.Position, T05Twintania.NeurolinkRadius)); - hints.AddForbiddenZone(p => -forbidden.Min(f => f(p))); + var forbidden = new List>(); + for (var i = 0; i < _hatch.Neurolinks.Count; ++i) + { + var neurolink = _hatch.Neurolinks[i]; + if (neurolink != neurolinkUnderBoss) + forbidden.Add(ShapeDistance.Circle(neurolink.Position, T05Twintania.NeurolinkRadius)); + } + hints.AddForbiddenZone(ShapeDistance.InvertedUnion(forbidden)); } } else if (assignment == ((_deathSentence?.TankedByOT ?? false) ? PartyRolesConfig.Assignment.MT : PartyRolesConfig.Assignment.OT) && neurolinkUnderBoss != null && actor != _liquidHell?.Target) diff --git a/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs b/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs index 99df8fd42e..156e43a4f8 100644 --- a/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs +++ b/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs @@ -95,7 +95,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme orbs.Add(ShapeDistance.InvertedCircle(o.Position + 0.5f * o.Rotation.ToDirection(), 0.56f)); } if (orbs.Count > 0) - hints.AddForbiddenZone(p => orbs.Max(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Intersection(orbs)); } public override void DrawArenaForeground(int pcSlot, Actor pc) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs index eb15eb3d54..86bbde93c1 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs @@ -87,7 +87,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme for (var i = 0; i < len; ++i) forbidden.Add(ShapeDistance.Rect(component[i].Origin, Module.PrimaryActor.Rotation, 40, 0, 6)); if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } @@ -109,7 +109,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme for (var i = 0; i < len; ++i) forbidden.Add(ShapeDistance.Rect(component[i].Origin, new WDir(0, 1), 40, 40, 6)); if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs index 8d85d86153..92a8677e90 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs @@ -76,7 +76,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme else forbidden.Add(ShapeDistance.Circle(o.Position, Radius)); } - hints.AddForbiddenZone(p => forbidden.Min(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } public override void OnActorCreated(Actor actor) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs b/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs index 5d13d3ff93..84088c0970 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs @@ -57,7 +57,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(count); for (var i = 0; i < count; ++i) forbidden.Add(ShapeDistance.Circle(Module.Enemies(OID.IronCage)[i].Position, 11)); - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), spread.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), spread.Activation); } } @@ -116,7 +116,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var a = cages[i]; forbidden.Add(ShapeDistance.Cone(bait.Source.Position, 100, bait.Source.AngleTo(a), Angle.Asin(3.5f / (a.Position - bait.Source.Position).Length()))); } - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), bait.Activation); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), bait.Activation); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs b/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs index 2582059705..6e337c7281 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs @@ -64,7 +64,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme for (var i = 0; i < count; ++i) orbs.Add(ShapeDistance.InvertedCircle(_orbs[i].Position, 0.7f)); var activation = _aoe.ActiveAOEs(slot, actor).FirstOrDefault().Activation.AddSeconds(1.1f); - hints.AddForbiddenZone(p => orbs.Max(f => f(p)), activation == default ? WorldState.FutureTime(2) : activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(orbs), activation == default ? WorldState.FutureTime(2) : activation); } public override void DrawArenaForeground(int pcSlot, Actor pc) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs index 8c7bf94c2f..d4ab8742d2 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs @@ -143,7 +143,7 @@ private void HandleNonTransportingActor(Actor actor, AIHints hints, IEnumerable< forbidden.Add(ShapeDistance.InvertedCircle(seed.Position, 3)); var distance = (actor.Position - closestSeed.Position).LengthSq(); if (forbidden.Count > 0 && distance > 9) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden)); else if (distance < 9) hints.InteractWithTarget = closestSeed; } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs index a0a4286b92..1e6bba6495 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs @@ -82,8 +82,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(Furniture.Count()); foreach (var h in Furniture) forbidden.Add(ShapeDistance.InvertedCircle(h.Position, h.HitboxRadius - 0.5f)); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p))); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden)); } public override void AddHints(int slot, Actor actor, TextHints hints) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D10AnamnesisAnyder/D103RukshsDheem.cs b/BossMod/Modules/Shadowbringers/Dungeon/D10AnamnesisAnyder/D103RukshsDheem.cs index 215d3946c6..dc032b3e70 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D10AnamnesisAnyder/D103RukshsDheem.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D10AnamnesisAnyder/D103RukshsDheem.cs @@ -229,19 +229,9 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme break; // can only block one drain at a time, no point to keep checking } } - float maxDistanceFunc(WPos pos) - { - var minDistance = float.MinValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance > minDistance) - minDistance = distance; - } - return minDistance; - } - if (forbidden.Count > 0) - hints.AddForbiddenZone(maxDistanceFunc, activation); + + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), activation); } public override void AddHints(int slot, Actor actor, TextHints hints) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D112SpectralNecromancer.cs b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D112SpectralNecromancer.cs index 153f9346d0..064c0bc1ae 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D112SpectralNecromancer.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D112SpectralNecromancer.cs @@ -95,8 +95,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(); foreach (var e in WorldState.Actors.Where(x => !x.IsAlly && x.Tether.ID == (uint)TetherID.CrawlingNecrobombs)) forbidden.Add(circle.Distance(e.Position, default)); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p))); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs index 51277d6a16..29a48f2476 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs @@ -143,7 +143,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var dir = source.Origin.X == 738 ? 1 : -1; forbidden.Add(ShapeDistance.InvertedDonutSector(source.Origin, 8, 9, a45 * dir, a10)); forbidden.Add(ShapeDistance.InvertedDonutSector(source.Origin, 8, 9, 3 * a45 * dir, a10)); - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), source.Activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs index d21f84c142..2f681e2a28 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs @@ -158,7 +158,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme for (var i = 0; i < activeHoles.Count; ++i) forbidden.Add(ShapeDistance.InvertedRect(b.Source.Position, activeHoles[i], 1)); if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden)); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs index c4e68e43cb..2ec7b5beda 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs @@ -80,7 +80,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme foreach (var a in Rods) forbidden.Add(ShapeDistance.InvertedCircle(a.Position, 4)); if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), activation); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), activation); } public override void DrawArenaForeground(int pcSlot, Actor pc) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs index 6f51f6ee6a..472397736f 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs @@ -119,7 +119,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var m = _missiles[i]; forbidden.Add(ShapeDistance.Capsule(m.Position, m.Rotation, Length, Radius)); // merging all forbidden zones into one to make pathfinding less demanding } - hints.AddForbiddenZone(p => forbidden.Min(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Union(forbidden)); } } diff --git a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE14VigilForLost.cs b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE14VigilForLost.cs index 30223b432a..bd9cbf2815 100644 --- a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE14VigilForLost.cs +++ b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE14VigilForLost.cs @@ -32,7 +32,7 @@ class Shockwave(BossModule module) : Components.SimpleAOEs(module, ActionID.Make class ExplosiveFlare(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.ExplosiveFlare), 10); class CripplingBlow(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.CripplingBlow)); class PlasmaField(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.PlasmaField)); -class Towers(BossModule module) : Components.CastTowers(module, ActionID.MakeSpell(AID.Explosion), 6); +class Towers(BossModule module) : Components.CastTowersOpenWorld(module, ActionID.MakeSpell(AID.Explosion), 6); class MagitekRay(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.MagitekRay), new AOEShapeRect(50, 2)); class CE14VigilForLostStates : StateMachineBuilder diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousUltros.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousUltros.cs index 98dbbb56ea..d4082ae769 100644 --- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousUltros.cs +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousUltros.cs @@ -68,8 +68,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, Angle.FromDirection(c.Origin - Module.Center), 30.Degrees())); } - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); } } } diff --git a/BossMod/Modules/Stormblood/Dungeon/D05CastrumAbania/D051MagnaRoader.cs b/BossMod/Modules/Stormblood/Dungeon/D05CastrumAbania/D051MagnaRoader.cs index 4842f66f2c..700f356abc 100644 --- a/BossMod/Modules/Stormblood/Dungeon/D05CastrumAbania/D051MagnaRoader.cs +++ b/BossMod/Modules/Stormblood/Dungeon/D05CastrumAbania/D051MagnaRoader.cs @@ -51,7 +51,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { var distance = (actor.Position - closestTurret.Position).LengthSq(); if (forbidden.Count > 0 && distance > 9) - hints.AddForbiddenZone(p => forbidden.Max(f => f(p))); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden)); else if (distance < 9) { hints.InteractWithTarget = closestTurret; diff --git a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtue.cs b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtue.cs index 340bc9a2d4..66f3a7158c 100644 --- a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtue.cs +++ b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtue.cs @@ -16,8 +16,9 @@ class ExplosiveImpulse1(BossModule module) : ExplosiveImpulse(module, AID.Explos class ExplosiveImpulse2(BossModule module) : ExplosiveImpulse(module, AID.ExplosiveImpulse2); class AernsWynavExplosion(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.ExplosionWyvern), "Aerns Wyvnav is enraging!", true); +class MeteorEnrageCounter(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.MeteorEnrageRepeat)); -[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.BaldesionArsenal, GroupID = 639, NameID = 7976, PlanLevel = 70)] +[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.BaldesionArsenal, GroupID = 639, NameID = 7976, PlanLevel = 70, SortOrder = 4)] public class BA3AbsoluteVirtue(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) { private static readonly ArenaBoundsComplex arena = new([new Polygon(new(-175, 314), 29.95f, 96), new Rectangle(new(-146, 314), 0.8f, 5.8f), new Rectangle(new(-175, 285), 6, 1.05f)], diff --git a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueEnums.cs b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueEnums.cs index 3c7327623b..4dd0d0674b 100644 --- a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueEnums.cs +++ b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueEnums.cs @@ -47,7 +47,7 @@ public enum AID : uint ExplosionWyvern = 14676, // AernsWynav->self, 8.0s cast, range 60 circle, add soft enrage MeteorEnrage = 14700, // Boss->self, 10.0s cast, range 60 circle - MeteorRepeat = 14703, // Boss->self, no cast, range 60 circle, repeat until everyone is dead + MeteorEnrageRepeat = 14703, // Boss->self, no cast, range 60 circle, repeat until everyone is dead } public enum TetherID : uint diff --git a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueStates.cs b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueStates.cs index ea750d34da..6d7ad74287 100644 --- a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueStates.cs +++ b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/BA3AbsoluteVirtueStates.cs @@ -13,11 +13,126 @@ public BA3AbsoluteVirtueStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); } - + // some timings have noticeable variations of over 1s private void SinglePhase(uint id) { - SimpleState(id + 0xFF0000, 10000, "???"); + Meteor(id, 11.1f); + Eidos(id + 0x10000, 4.2f); + HostileAspect1(id + 0x20000, 4.1f); + MedusaJavelin(id + 0x30000, 2.7f); + Eidos(id + 0x40000, 5.2f); + ImpactStreamBoss(id + 0x50000, 4); + AuroralWind(id + 0x60000, 6.4f); + Eidos(id + 0x70000, 12); + HostileAspect1(id + 0x80000, 3.8f); + Meteor(id + 0x90000, 14); + DarkBrightAuroraTowers(id + 0xA0000, 10); + MedusaJavelin(id + 0xB0000, 5.8f); + AuroralWind(id + 0xC0000, 3.5f); + Meteor(id + 0xD0000, 4.1f); + Meteor(id + 0xE0000, 5.1f); + RelativeVirtues(id + 0xF0000, 10); + MedusaJavelin(id + 0x100000, 3.2f); + AuroralWind(id + 0x110000, 7); + Meteor(id + 0x120000, 4.2f); + CallWyvern(id + 0x130000, 6.5f); + DarkBrightAuroraTowers(id + 0x140000, 6.4f); + MedusaJavelin(id + 0x150000, 5); + AuroralWind(id + 0x160000, 3.1f); + Meteor(id + 0x170000, 13); + Eidos(id + 0x180000, 4.2f); + HostileAspect2(id + 0x190000, 5); + MedusaJavelin(id + 0x1A0000, 8.2f); + Meteor(id + 0x1B0000, 3.2f); + CallWyvern(id + 0x1C0000, 12.3f); + DarkBrightAuroraTowers(id + 0x1D0000, 6.2f); + MedusaJavelin(id + 0x1E0000, 5); + AuroralWind(id + 0x1F0000, 3.2f); + Meteor(id + 0x200000, 13.2f); + Eidos(id + 0x210000, 4.2f); + HostileAspect2(id + 0x220000, 3.5f); + MedusaJavelin(id + 0x230000, 8); + Meteor(id + 0x240000, 3); + MeteorEnrage(id + 0x250000); + } + + private void Meteor(uint id, float delay) + { + Cast(id, AID.Meteor, delay, 4, "Raidwide") + .SetHint(StateMachine.StateHint.Raidwide); + } + + private void MeteorEnrage(uint id) + { + Cast(id, AID.MeteorEnrage, 3.1f, 10, "Enrage") + .ActivateOnEnter(); + ComponentCondition(id + 0x10, 3, comp => comp.NumCasts == 1, "Enrage repeat 1"); + ComponentCondition(id + 0x20, 5, comp => comp.NumCasts == 2, "Enrage repeat 2"); + } + + private void Eidos(uint id, float delay) + { + CastMulti(id, [AID.EidosAstral, AID.EidosUmbral], delay, 2, "Change element"); + } + + private void HostileAspect1(uint id, float delay) + { + Cast(id, AID.HostileAspect, delay, 8, "Circle AOEs"); + } + + private void HostileAspect2(uint id, float delay) + { + CastStart(id, AID.HostileAspect, delay, "Circle AOEs appear"); + ComponentCondition(id + 0x10, 7.4f, comp => comp.Casters.Count != 0, "Proximity AOE appears") + .ActivateOnEnter(); + CastEnd(id + 0x20, 2, "Circle AOEs resolve"); + ComponentCondition(id + 0x30, 4.4f, comp => comp.Casters.Count == 0, "Proximity AOE resolves"); + CastMulti(id + 0x40, [AID.EidosAstral, AID.EidosUmbral], 6.3f, 2, "Change element"); + ComponentCondition(id + 0x50, 1.9f, comp => comp.NumCasts == 2, "Half room cleaves 1"); + ComponentCondition(id + 0x60, 4.8f, comp => comp.NumCasts == 4, "Half room cleaves 2") + .DeactivateOnExit(); + } + + private void MedusaJavelin(uint id, float delay) + { + Cast(id, AID.MedusaJavelin, delay, 3, "Cone AOE"); + } + + private void AuroralWind(uint id, float delay) + { + Cast(id, AID.AuroralWind, delay, 5, "Tankbuster") + .SetHint(StateMachine.StateHint.Tankbuster); + } + + private void ImpactStreamBoss(uint id, float delay) + { + Cast(id, AID.ImpactStream1, delay, 3, "Half room cleaves"); + } + + private void DarkBrightAuroraTowers(uint id, float delay) + { + ComponentCondition(id, delay, comp => comp.Towers.Count != 0, "Towers + tethers appear"); + } + + private void RelativeVirtues(uint id, float delay) + { + ComponentCondition(id, delay, comp => comp.Casters.Count != 0, "Proximity AOEs appear") + .ActivateOnEnter(); + ComponentCondition(id + 0x10, 5, comp => comp.Casters.Count == 0, "Proximity AOEs resolve"); + ComponentCondition(id + 0x20, 8, comp => comp.NumCasts == 2, "Half room cleaves 1"); + ComponentCondition(id + 0x30, 5.6f, comp => comp.NumCasts == 4, "Half room cleaves 2"); + ComponentCondition(id + 0x40, 4.4f, comp => comp.NumCasts == 6, "Half room cleaves 3") + .DeactivateOnExit(); + CastStart(id + 0x50, AID.ExplosiveImpulse2, 1.3f, "Proximity AOE appears"); + CastEnd(id + 0x60, 5, "Proximity AOE resolves"); + } + + private void CallWyvern(uint id, float delay) + { + Cast(id, AID.CallWyvern, delay, 3, "Adds spawn"); } } diff --git a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/DarkBrightAuroraTowers.cs b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/DarkBrightAuroraTowers.cs new file mode 100644 index 0000000000..819afe9070 --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/DarkBrightAuroraTowers.cs @@ -0,0 +1,168 @@ +namespace BossMod.Stormblood.Foray.BaldesionArsenal.BA3AbsoluteVirtue; + +class BrightDarkAuroraExplosion(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeCircle circle = new(8); + private readonly List<(Actor source, ulong target)> tetherByActor = new(8); + + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = tetherByActor.Count; + if (count == 0) + return []; + + var isActorTarget = false; + + for (var i = 0; i < count; ++i) + { + var tether = tetherByActor[i]; + if (tether.target == actor.InstanceID) + { + isActorTarget = true; + break; + } + } + + List aoes = new(count); + for (var i = 0; i < count; ++i) + { + var tether = tetherByActor[i]; + if (tether.target != actor.InstanceID) + aoes.Add(new(circle, tetherByActor[i].source.Position, Risky: !isActorTarget)); + } + return aoes; + } + + public override void OnTethered(Actor source, ActorTetherInfo tether) + { + tetherByActor.Add((source, tether.Target)); + } + + public override void OnUntethered(Actor source, ActorTetherInfo tether) + { + tetherByActor.Remove((source, tether.Target)); + } +} + +abstract class Towers(BossModule module, OID oid, TetherID tid) : Components.GenericTowersOpenWorld(module) +{ + private readonly List<(Actor source, Actor target)> tetherByActor = new(4); + private const string Hint = "Stand in a tower of opposite tether element!"; + + public override void OnActorEAnim(Actor actor, uint state) + { + if ((OID)actor.OID == oid) + { + if (state == 0x00040008) + { + for (var i = 0; i < Towers.Count; ++i) + { + var tower = Towers[i]; + if (tower.Position == actor.Position) + { + Towers.Remove(tower); + break; + } + } + } + } + } + + public override void OnActorCreated(Actor actor) + { + if ((OID)actor.OID == oid) + Towers.Add(new(actor.Position, 2, 1, 1, [], WorldState.FutureTime(20))); + } + + public override void OnTethered(Actor source, ActorTetherInfo tether) + { + if (tether.ID == (uint)tid) + tetherByActor.Add((source, WorldState.Actors.Find(tether.Target)!)); + } + + public override void OnUntethered(Actor source, ActorTetherInfo tether) + { + if (tether.ID == (uint)tid) + tetherByActor.Remove((source, WorldState.Actors.Find(tether.Target)!)); + } + + public override void AddHints(int slot, Actor actor, TextHints hints) + { + var count = tetherByActor.Count; + if (count == 0) + return; + + var isActorTarget = false; + + for (var i = 0; i < count; ++i) + { + var tether = tetherByActor[i]; + if (tether.target == actor) + { + isActorTarget = true; + break; + } + } + + if (isActorTarget) + { + var soakedIndex = -1; + for (var i = 0; i < Towers.Count; ++i) + { + var t = Towers[i]; + var allowedSoakers = t.AllowedSoakers ??= Tower.Soakers(Module); + if (allowedSoakers.Contains(actor) && t.IsInside(actor)) + { + soakedIndex = i; + break; + } + } + if (soakedIndex == -1) + hints.Add(Hint); + else + hints.Add(Hint, false); + } + else + base.AddHints(slot, actor, hints); + } + + public override void DrawArenaForeground(int pcSlot, Actor pc) + { + base.DrawArenaForeground(pcSlot, pc); + var count = tetherByActor.Count; + if (count == 0) + return; + + Actor? source = null; + for (var i = 0; i < count; ++i) + { + var tether = tetherByActor[i]; + if (tether.target == pc) + { + source = tether.source; + break; + } + } + if (source != null) + { + Arena.AddLine(source.Position, pc.Position); + Arena.AddCircle(source.Position, 2); + Arena.Actor(source, Colors.Object, true); + } + } + + public override void Update() + { + var count = Towers.Count; + if (count == 0) + return; + HashSet allowed = new(4); + for (var i = 0; i < tetherByActor.Count; ++i) + allowed.Add(tetherByActor[i].target); + for (var i = 0; i < count; ++i) + Towers[i] = Towers[i] with { AllowedSoakers = allowed }; + } +} + +class BrightAuroraTether(BossModule module) : Towers(module, OID.DarkAuroraHelper, TetherID.BrightAurora); +class DarkAuroraTether(BossModule module) : Towers(module, OID.BrightAuroraHelper, TetherID.DarkAurora); diff --git a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspect.cs b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspect.cs deleted file mode 100644 index 3e4e740a2c..0000000000 --- a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspect.cs +++ /dev/null @@ -1,278 +0,0 @@ -namespace BossMod.Stormblood.Foray.BaldesionArsenal.BA3AbsoluteVirtue; - -class BrightDarkAurora(BossModule module) : Components.GenericAOEs(module) -{ - private static readonly AOEShapeRect rect = new(30, 50); - public readonly List _aoes = new(3); - - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; - - public override void OnCastStarted(Actor caster, ActorCastInfo spell) - { - void AddAOE() => _aoes.Add(new(rect, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); - switch ((AID)spell.Action.ID) - { - case AID.DarkAurora1: - case AID.DarkAurora2: - if (caster.FindStatus(SID.UmbralEssence) != null) - AddAOE(); - break; - case AID.BrightAurora1: - case AID.BrightAurora2: - if (caster.FindStatus(SID.AstralEssence) != null) - AddAOE(); - break; - } - } - - public override void OnCastFinished(Actor caster, ActorCastInfo spell) - { - if (_aoes.Count != 0 && (AID)spell.Action.ID is AID.BrightAurora1 or AID.BrightAurora2 or AID.DarkAurora1 or AID.DarkAurora2) - _aoes.RemoveAt(0); - } -} - -class AstralUmbralRays(BossModule module) : Components.GenericAOEs(module) -{ - private static readonly AOEShapeCircle circleSmall = new(8), circleBig = new(16); - public readonly List _aoes = new(9); - - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; - - public override void OnCastStarted(Actor caster, ActorCastInfo spell) - { - void AddAOE(bool big) => _aoes.Add(new(big ? circleBig : circleSmall, spell.LocXZ, default, Module.CastFinishAt(spell))); - switch ((AID)spell.Action.ID) - { - case AID.UmbralRays1: - case AID.UmbralRays2: - AddAOE(Module.PrimaryActor.FindStatus(SID.UmbralEssence) != null); - break; - case AID.AstralRays1: - case AID.AstralRays2: - AddAOE(Module.PrimaryActor.FindStatus(SID.AstralEssence) != null); - break; - } - } - - public override void OnCastFinished(Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID is AID.UmbralRays1 or AID.UmbralRays2 or AID.AstralRays1 or AID.AstralRays2) - _aoes.Clear(); - } -} - -class BrightDarkAuroraTethers(BossModule module) : Components.GenericAOEs(module) -{ - private static readonly AOEShapeCircle circlePuddle = new(2), circleAOE = new(8); - private readonly List<(Actor actor, bool occupied, bool isLight)> puddles = new(8); - private readonly List<(Actor source, ulong target, bool isLightTether)> tetherByActor = new(8); - - public override IEnumerable ActiveAOEs(int slot, Actor actor) - { - var count = tetherByActor.Count; - if (count == 0) - return []; - - var isActorTarget = false; - var isLightTether = false; - - for (var i = 0; i < count; ++i) - { - var tether = tetherByActor[i]; - if (tether.target == actor.InstanceID) - { - isActorTarget = true; - isLightTether = tether.isLightTether; - break; - } - } - var puddlecount = puddles.Count; - - List aoes = new(count + puddlecount); - for (var i = 0; i < count; ++i) - { - var tether = tetherByActor[i]; - if (tether.target != actor.InstanceID) - aoes.Add(new(circleAOE, tetherByActor[i].source.Position, Risky: !isActorTarget)); - } - - if (!isActorTarget) - { - for (var i = 0; i < puddlecount; ++i) - aoes.Add(new(circlePuddle, puddles[i].actor.Position, Risky: false)); - } - else - { - for (var i = 0; i < puddlecount; ++i) - { - var puddle = puddles[i]; - var isSafe = puddle.isLight != isLightTether && (IsOccupyingPuddle(actor, puddle.actor.Position) || !puddle.occupied); - aoes.Add(new(circlePuddle, puddle.actor.Position, Color: isSafe ? Colors.SafeFromAOE : 0, Risky: false)); - } - } - return aoes; - } - - public static bool IsOccupyingPuddle(Actor actor, WPos pos) => actor.Position.InCircle(pos, 2); - - public override void OnActorEAnim(Actor actor, uint state) - { - if ((OID)actor.OID is OID.BrightAuroraHelper or OID.DarkAuroraHelper) - { - if (state is 0x00100020 or 0x00400080) // puddle becomes occupied / unoccupied - { - for (var i = 0; i < puddles.Count; ++i) - { - var puddle = puddles[i]; - if (puddle.actor == actor) - { - puddles[i] = puddle with { occupied = state == 0x00100020 }; - break; - } - } - } - else if (state == 0x00040008) // puddle disappears - { - for (var i = 0; i < puddles.Count; ++i) - { - var puddle = puddles[i]; - if (puddle.actor == actor) - { - puddles.Remove(puddle); - break; - } - } - } - } - } - - public override void OnActorCreated(Actor actor) - { - if ((OID)actor.OID is OID.BrightAuroraHelper or OID.DarkAuroraHelper) - puddles.Add(new(actor, false, (OID)actor.OID == OID.BrightAuroraHelper)); - } - - public override void OnTethered(Actor source, ActorTetherInfo tether) - { - tetherByActor.Add((source, tether.Target, tether.ID == (uint)TetherID.BrightAurora)); - } - - public override void OnUntethered(Actor source, ActorTetherInfo tether) - { - tetherByActor.Remove((source, tether.Target, tether.ID == (uint)TetherID.BrightAurora)); - } - - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) - { - var count = tetherByActor.Count; - if (count == 0) - return; - base.AddAIHints(slot, actor, assignment, hints); - var puddleCount = puddles.Count; - - var isActorTarget = false; - var isLightTether = false; - - for (var i = 0; i < count; ++i) - { - var tether = tetherByActor[i]; - if (tether.target == actor.InstanceID) - { - isActorTarget = true; - isLightTether = tether.isLightTether; - break; - } - } - var forbiddenInverted = new List>(8); - var forbidden = new List>(8); - for (var i = 0; i < puddleCount; ++i) - { - var puddle = puddles[i]; - if (isActorTarget && puddle.isLight != isLightTether && (IsOccupyingPuddle(actor, puddle.actor.Position) || !puddle.occupied)) - { - forbiddenInverted.Add(ShapeDistance.InvertedCircle(puddle.actor.Position, 2)); - } - else if (!isActorTarget) - forbidden.Add(ShapeDistance.Circle(puddle.actor.Position, 2)); - } - float maxDistanceFunc(WPos pos) - { - var minDistance = float.MinValue; - for (var i = 0; i < forbiddenInverted.Count; ++i) - { - var distance = forbiddenInverted[i](pos); - if (distance > minDistance) - minDistance = distance; - } - return minDistance; - } - float minDistanceFunc(WPos pos) - { - var minDistance = float.MaxValue; - for (var i = 0; i < forbidden.Count; ++i) - { - var distance = forbidden[i](pos); - if (distance < minDistance) - minDistance = distance; - } - return minDistance; - } - var activation = WorldState.FutureTime(5); - if (forbiddenInverted.Count != 0) - hints.AddForbiddenZone(maxDistanceFunc, activation); - if (forbidden.Count != 0) - hints.AddForbiddenZone(minDistanceFunc, activation); - } - - public override void AddHints(int slot, Actor actor, TextHints hints) - { - var count = tetherByActor.Count; - if (count == 0) - return; - - var isActorTarget = false; - var isLightTether = false; - - for (var i = 0; i < count; ++i) - { - var tether = tetherByActor[i]; - if (tether.target == actor.InstanceID) - { - isActorTarget = true; - isLightTether = tether.isLightTether; - break; - } - } - - if (isActorTarget) - hints.Add($"Stand in a {(isLightTether ? "dark" : "bright")} puddle!"); - else - base.AddHints(slot, actor, hints); - } - - public override void DrawArenaForeground(int pcSlot, Actor pc) - { - base.DrawArenaForeground(pcSlot, pc); - var count = tetherByActor.Count; - if (count == 0) - return; - - Actor? source = null; - for (var i = 0; i < count; ++i) - { - var tether = tetherByActor[i]; - if (tether.target == pc.InstanceID) - { - source = tether.source; - - break; - } - } - if (source != null) - { - Arena.AddLine(source.Position, pc.Position); - Arena.Actor(source, Colors.Object, true); - } - } -} diff --git a/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspectAOEs.cs b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspectAOEs.cs new file mode 100644 index 0000000000..be613f2cfb --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/BaldesionsArsenal/BA3AbsoluteVirtue/UmbralAstralAspectAOEs.cs @@ -0,0 +1,66 @@ +namespace BossMod.Stormblood.Foray.BaldesionArsenal.BA3AbsoluteVirtue; + +class BrightDarkAurora(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeRect rect = new(30, 50); + public readonly List _aoes = new(3); + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + void AddAOE() => _aoes.Add(new(rect, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); + switch ((AID)spell.Action.ID) + { + case AID.DarkAurora1: + case AID.DarkAurora2: + if (caster.FindStatus(SID.UmbralEssence) != null) + AddAOE(); + break; + case AID.BrightAurora1: + case AID.BrightAurora2: + if (caster.FindStatus(SID.AstralEssence) != null) + AddAOE(); + break; + } + } + + public override void OnCastFinished(Actor caster, ActorCastInfo spell) + { + if (_aoes.Count != 0 && (AID)spell.Action.ID is AID.BrightAurora1 or AID.BrightAurora2 or AID.DarkAurora1 or AID.DarkAurora2) + _aoes.RemoveAt(0); + } +} + +class BrightDarkAuroraCounter(BossModule module) : Components.CastCounterMulti(module, [ActionID.MakeSpell(AID.DarkAurora1), ActionID.MakeSpell(AID.BrightAurora1), +ActionID.MakeSpell(AID.DarkAurora2), ActionID.MakeSpell(AID.BrightAurora2)]); + +class AstralUmbralRays(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeCircle circleSmall = new(8), circleBig = new(16); + public readonly List _aoes = new(9); + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + void AddAOE(bool big) => _aoes.Add(new(big ? circleBig : circleSmall, spell.LocXZ, default, Module.CastFinishAt(spell))); + switch ((AID)spell.Action.ID) + { + case AID.UmbralRays1: + case AID.UmbralRays2: + AddAOE(Module.PrimaryActor.FindStatus(SID.UmbralEssence) != null); + break; + case AID.AstralRays1: + case AID.AstralRays2: + AddAOE(Module.PrimaryActor.FindStatus(SID.AstralEssence) != null); + break; + } + } + + public override void OnCastFinished(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.UmbralRays1 or AID.UmbralRays2 or AID.AstralRays1 or AID.AstralRays2) + _aoes.Clear(); + } +} diff --git a/BossMod/Util/ShapeDistance.cs b/BossMod/Util/ShapeDistance.cs index f92a791f4d..35cfbc29df 100644 --- a/BossMod/Util/ShapeDistance.cs +++ b/BossMod/Util/ShapeDistance.cs @@ -638,8 +638,7 @@ public static Func InvertedCross(WPos origin, Angle direction, floa }; } - // positive offset increases area - public static Func ConvexPolygon(List<(WPos, WPos)> edges, bool cw, float offset = 0) + public static Func ConvexPolygon(List<(WPos, WPos)> edges, bool cw) { Func edge((WPos p1, WPos p2) e) { @@ -649,13 +648,55 @@ Func edge((WPos p1, WPos p2) e) var normal = cw ? dir.OrthoL() : dir.OrthoR(); return p => normal.Dot(p - e.p1); } - return Intersection([.. edges.Select(edge)], offset); + return Intersection([.. edges.Select(edge)]); } - public static Func ConvexPolygon(ReadOnlySpan vertices, bool cw, float offset = 0) => ConvexPolygon(PolygonUtil.EnumerateEdges(vertices), cw, offset); + public static Func ConvexPolygon(ReadOnlySpan vertices, bool cw) => ConvexPolygon(PolygonUtil.EnumerateEdges(vertices), cw); - public static Func Intersection(List> funcs, float offset = 0) => p => funcs.Max(e => e(p)) - offset; - public static Func Union(List> funcs, float offset = 0) => p => funcs.Min(e => e(p)) - offset; + public static Func Intersection(List> funcs) // max distance func + { + return p => + { + var maxDistance = float.MinValue; + for (var i = 0; i < funcs.Count; ++i) + { + var distance = funcs[i](p); + if (distance > maxDistance) + maxDistance = distance; + } + return maxDistance; + }; + } + + public static Func Union(List> funcs) // min distance func + { + return p => + { + var minDistance = float.MaxValue; + for (var i = 0; i < funcs.Count; ++i) + { + var distance = funcs[i](p); + if (distance < minDistance) + minDistance = distance; + } + return minDistance; + }; + } + + public static Func InvertedUnion(List> funcs) // min distance func + { + return p => + { + var minDistance = float.MaxValue; + for (var i = 0; i < funcs.Count; ++i) + { + var distance = funcs[i](p); + if (distance < minDistance) + minDistance = distance; + } + return -minDistance; + }; + } // special distance function for precise positioning, finer than map resolution // it's an inverted rect of a size equal to one grid cell, with a special adjustment if starting position is in the same cell, but farther than tolerance