From 3afeb4e04b59ee804bfdd9687021d32e6d56627b Mon Sep 17 00:00:00 2001 From: CarnifexOptimus <156172553+CarnifexOptimus@users.noreply.github.com> Date: Sun, 5 Jan 2025 05:48:43 +0100 Subject: [PATCH] some cleanup --- BossMod/BossModule/AOEShapes.cs | 26 +- BossMod/BossModule/ArenaBounds.cs | 4 +- BossMod/Components/StackSpread.cs | 77 +++++- BossMod/Components/Towers.cs | 255 +++++++++++++++--- BossMod/Data/ActorEnumeration.cs | 45 ++++ BossMod/Data/PartyState.cs | 29 +- .../D074GreatestSerpentOfTural.cs | 2 +- .../Savage/M03SBruteBomber/TagTeam.cs | 8 +- .../Savage/M04SWickedThunder/ElectropeEdge.cs | 12 +- .../T01Valigarmanda/ChillingCataclysm.cs | 5 +- .../D06DeadEnds/D061CausticGrebuloff.cs | 2 +- .../Extreme/Ex7Zeromus/VoidMeteor.cs | 2 +- .../V02MR/V024Shishio/FocusedTremor.cs | 6 +- .../V026ShishuChochin/V026ShishuChochin.cs | 2 +- .../Stage32AGoldenOpportunity/Stage32Act2.cs | 10 +- .../Dungeon/D11Antitower/D112Ziggy.cs | 2 +- .../Dungeon/D13SohrKhai/D132Poqhiraj.cs | 4 +- .../Dungeon/D15Xelphatol/D152DotoliCiloc.cs | 2 +- .../D11HeroesGauntlet/D111SpectralThief.cs | 1 - .../D113SpectralBerserker.cs | 2 +- .../DRS7StygimolochLord/Border.cs | 4 +- .../D022RubyPrincess.cs | 2 +- .../Stormblood/Dungeon/D13Burn/D131Hedetet.cs | 5 +- 23 files changed, 406 insertions(+), 101 deletions(-) diff --git a/BossMod/BossModule/AOEShapes.cs b/BossMod/BossModule/AOEShapes.cs index 79fa1dcb6b..d6fa3b7967 100644 --- a/BossMod/BossModule/AOEShapes.cs +++ b/BossMod/BossModule/AOEShapes.cs @@ -190,13 +190,14 @@ public enum OperandType // shapes1 for unions, shapes 2 for shapes for XOR/intersection with shapes1, differences for shapes that get subtracted after previous operations // always create a new instance of AOEShapeCustom if something other than the invertforbiddenzone changes // if the origin of the AOE can change, edit the origin default value to prevent cache issues -public sealed record class AOEShapeCustom(IEnumerable Shapes1, IEnumerable? DifferenceShapes = null, IEnumerable? Shapes2 = null, bool InvertForbiddenZone = false, OperandType Operand = OperandType.Union, WPos Origin = default) : AOEShape +public sealed record class AOEShapeCustom(Shape[] Shapes1, Shape[]? DifferenceShapes = null, Shape[]? Shapes2 = null, bool InvertForbiddenZone = false, OperandType Operand = OperandType.Union, WPos Origin = default) : AOEShape { public RelSimplifiedComplexPolygon? Polygon; private PolygonWithHolesDistanceFunction? shapeDistance; private readonly int hashkey = CreateCacheKey(Shapes1, Shapes2 ?? [], DifferenceShapes ?? [], Operand, Origin); private static readonly Dictionary cache = []; private static readonly LinkedList cacheOrder = new(); + public void AddToCache(RelSimplifiedComplexPolygon value) { if (cache.Count >= 50) @@ -246,8 +247,9 @@ public RelSimplifiedComplexPolygon GetCombinedPolygon(WPos origin) if (Shapes2 != null) { Polygon = clipper.Simplify(shapes1); - foreach (var shape in Shapes2) + for (var i = 0; i < Shapes2.Length; ++i) { + var shape = Shapes2[i]; var singleShapeOperand = CreateOperandFromShape(shape, origin); switch (Operand) @@ -274,27 +276,29 @@ private static PolygonClipper.Operand CreateOperandFromShape(Shape shape, WPos o return operand; } - private static PolygonClipper.Operand CreateOperandFromShapes(IEnumerable? shapes, WPos origin) + private static PolygonClipper.Operand CreateOperandFromShapes(Shape[]? shapes, WPos origin) { var operand = new PolygonClipper.Operand(); if (shapes != null) - foreach (var shape in shapes) - operand.AddPolygon(shape.ToPolygon(origin)); + for (var i = 0; i < shapes.Length; ++i) + operand.AddPolygon(shapes[i].ToPolygon(origin)); return operand; } public override bool Check(WPos position, WPos origin, Angle rotation) { - var (x, z) = position - origin; - var result = (Polygon ?? GetCombinedPolygon(origin)).Contains(new(x, z)); - return result; + return (Polygon ?? GetCombinedPolygon(origin)).Contains(position - origin); } - private static int CreateCacheKey(IEnumerable shapes1, IEnumerable shapes2, IEnumerable differenceShapes, OperandType operand, WPos origin) + private static int CreateCacheKey(Shape[] shapes1, Shape[] shapes2, Shape[] differenceShapes, OperandType operand, WPos origin) { var hashCode = new HashCode(); - foreach (var shape in shapes1.Concat(shapes2).Concat(differenceShapes)) - hashCode.Add(shape.GetHashCode()); + for (var i = 0; i < shapes1.Length; ++i) + hashCode.Add(shapes1[i].GetHashCode()); + for (var i = 0; i < shapes2.Length; ++i) + hashCode.Add(shapes2[i].GetHashCode()); + for (var i = 0; i < differenceShapes.Length; ++i) + hashCode.Add(differenceShapes[i].GetHashCode()); hashCode.Add(operand); hashCode.Add(origin); return hashCode.ToHashCode(); diff --git a/BossMod/BossModule/ArenaBounds.cs b/BossMod/BossModule/ArenaBounds.cs index 8f23738444..202991a9dc 100644 --- a/BossMod/BossModule/ArenaBounds.cs +++ b/BossMod/BossModule/ArenaBounds.cs @@ -392,8 +392,8 @@ private static (WPos Center, float HalfWidth, float HalfHeight, float Radius, Re var combinedPoly = CombinePolygons(unionPolygons, differencePolygons, additionalPolygons); float minX = float.MaxValue, maxX = float.MinValue, minZ = float.MaxValue, maxZ = float.MinValue; - var count = combinedPoly.Parts.Count; - for (var i = 0; i < count; ++i) + + for (var i = 0; i < combinedPoly.Parts.Count; ++i) { var part = combinedPoly.Parts[i]; for (var j = 0; j < part.Exterior.Length; ++j) diff --git a/BossMod/Components/StackSpread.cs b/BossMod/Components/StackSpread.cs index 814734984d..9b0249eb9c 100644 --- a/BossMod/Components/StackSpread.cs +++ b/BossMod/Components/StackSpread.cs @@ -13,7 +13,18 @@ public struct Stack(Actor target, float radius, int minSize = 2, int maxSize = i public DateTime Activation = activation; public BitMask ForbiddenPlayers = forbiddenPlayers; // raid members that aren't allowed to participate in the stack - public readonly int NumInside(BossModule module) => module.Raid.WithSlot().ExcludedFromMask(ForbiddenPlayers).InRadius(Target.Position, Radius).Count(); + public readonly int NumInside(BossModule module) + { + var count = 0; + var party = module.Raid.WithSlot(); + for (var i = 0; i < party.Length; ++i) + { + var indexActor = party[i]; + if (!ForbiddenPlayers[indexActor.Item1] && indexActor.Item2.Position.InCircle(Target.Position, Radius)) + ++count; + } + return count; + } public readonly bool CorrectAmountInside(BossModule module) => NumInside(module) is var count && count >= MinSize && count <= MaxSize; public readonly bool InsufficientAmountInside(BossModule module) => NumInside(module) is var count && count < MaxSize; public readonly bool TooManyInside(BossModule module) => NumInside(module) is var count && count > MaxSize; @@ -36,11 +47,67 @@ public record struct Spread( public const string StackHint = "Stack!"; public bool Active => Stacks.Count + Spreads.Count > 0; - public IEnumerable ActiveStacks => IncludeDeadTargets ? Stacks : Stacks.Where(s => !s.Target.IsDead); - public IEnumerable ActiveSpreads => IncludeDeadTargets ? Spreads : Spreads.Where(s => !s.Target.IsDead); + public List ActiveStacks + { + get + { + if (IncludeDeadTargets) + return Stacks; + else + { + var count = Stacks.Count; + var activeStacks = new List(count); + for (var i = 0; i < count; ++i) + { + var stack = Stacks[i]; + if (!stack.Target.IsDead) + activeStacks.Add(stack); + } + return activeStacks; + } + } + } + + public List ActiveSpreads + { + get + { + if (IncludeDeadTargets) + return Spreads; + else + { + var count = Spreads.Count; + var activeSpreads = new List(count); + for (var i = 0; i < count; ++i) + { + var spread = Spreads[i]; + if (!spread.Target.IsDead) + activeSpreads.Add(spread); + } + return activeSpreads; + } + } + } + + public bool IsStackTarget(Actor? actor) + { + for (var i = 0; i < Stacks.Count; ++i) + { + if (Stacks[i].Target == actor) + return true; + } + return false; + } - public bool IsStackTarget(Actor? actor) => Stacks.Any(s => s.Target == actor); - public bool IsSpreadTarget(Actor? actor) => Spreads.Any(s => s.Target == actor); + public bool IsSpreadTarget(Actor? actor) + { + for (var i = 0; i < Spreads.Count; ++i) + { + if (Spreads[i].Target == actor) + return true; + } + return false; + } public override void AddHints(int slot, Actor actor, TextHints hints) { diff --git a/BossMod/Components/Towers.cs b/BossMod/Components/Towers.cs index 19bb38b2da..f66c1c0311 100644 --- a/BossMod/Components/Towers.cs +++ b/BossMod/Components/Towers.cs @@ -13,7 +13,19 @@ public struct Tower(WPos position, float radius, int minSoakers = 1, int maxSoak public readonly bool IsInside(WPos pos) => pos.InCircle(Position, Radius); public readonly bool IsInside(Actor actor) => IsInside(actor.Position); - public readonly int NumInside(BossModule module) => module.Raid.WithSlot().ExcludedFromMask(ForbiddenSoakers).InRadius(Position, Radius).Count(); + + public readonly int NumInside(BossModule module) + { + var count = 0; + var party = module.Raid.WithSlot(); + for (var i = 0; i < party.Length; ++i) + { + var indexActor = party[i]; + if (!ForbiddenSoakers[indexActor.Item1] && indexActor.Item2.Position.InCircle(Position, Radius)) + ++count; + } + return count; + } public readonly bool CorrectAmountInside(BossModule module) => NumInside(module) is var count && count >= MinSoakers && count <= MaxSoakers; public readonly bool InsufficientAmountInside(BossModule module) => NumInside(module) is var count && count < MaxSoakers; public readonly bool TooManyInside(BossModule module) => NumInside(module) is var count && count > MaxSoakers; @@ -30,30 +42,75 @@ public static void DrawTower(MiniArena arena, WPos pos, float radius, bool safe) public override void AddHints(int slot, Actor actor, TextHints hints) { - if (Towers.Any(t => t.ForbiddenSoakers[slot] && t.IsInside(actor))) + var count = Towers.Count; + if (count == 0) + return; + var gtfoFromTower = false; + for (var i = 0; i < count; ++i) { - hints.Add("GTFO from tower!"); + var t = Towers[i]; + if (t.ForbiddenSoakers[slot] && t.IsInside(actor)) + { + gtfoFromTower = true; + break; + } } - else if (Towers.FindIndex(t => !t.ForbiddenSoakers[slot] && t.IsInside(actor)) is var soakedIndex && soakedIndex >= 0) // note: this assumes towers don't overlap + + if (gtfoFromTower) { - var count = Towers[soakedIndex].NumInside(Module); - if (count < Towers[soakedIndex].MinSoakers) - hints.Add("Too few soakers in the tower!"); - else if (count > Towers[soakedIndex].MaxSoakers) - hints.Add("Too many soakers in the tower!"); - else - hints.Add("Soak the tower!", false); + hints.Add("GTFO from tower!"); } - else if (Towers.Any(t => !t.ForbiddenSoakers[slot] && t.InsufficientAmountInside(Module))) + else // Find index of a tower that is not forbidden and the actor is inside { - hints.Add("Soak the tower!"); + var soakedIndex = -1; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + if (!t.ForbiddenSoakers[slot] && 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]; + if (!t.ForbiddenSoakers[slot] && t.InsufficientAmountInside(Module)) + { + insufficientSoakers = true; + break; + } + } + if (insufficientSoakers) + hints.Add("Soak the tower!"); + } } } public override void DrawArenaForeground(int pcSlot, Actor pc) { - foreach (var t in Towers) + var count = Towers.Count; + if (count == 0) + return; + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; DrawTower(Arena, t.Position, t.Radius, !t.ForbiddenSoakers[pcSlot] && !t.IsInside(pc) && t.NumInside(Module) < t.MaxSoakers || t.IsInside(pc) && !t.ForbiddenSoakers[pcSlot] && t.NumInside(Module) <= t.MaxSoakers); + } } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -61,42 +118,162 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var count = Towers.Count; if (count == 0) return; - var forbiddenInverted = new List>(); - var forbidden = new List>(); - if (!Towers.Any(x => x.ForbiddenSoakers[slot])) + var forbiddenInverted = new List>(count); + var forbidden = new List>(count); + + var hasForbiddenSoakers = false; + for (var i = 0; i < count; ++i) + { + if (Towers[i].ForbiddenSoakers[slot]) + { + hasForbiddenSoakers = true; + break; + } + } + if (!hasForbiddenSoakers) { if (PrioritizeInsufficient) { - List insufficientTowers = []; - foreach (var t in Towers.Where(x => x.InsufficientAmountInside(Module) && x.NumInside(Module) > 0)) - insufficientTowers.Add(t); - var mostRelevantTower = insufficientTowers.OrderByDescending(x => x.NumInside(Module)).ThenBy(x => (x.Position - actor.Position).LengthSq()).FirstOrDefault(); - if (insufficientTowers.Count > 0) - forbiddenInverted.Add(ShapeDistance.InvertedCircle(mostRelevantTower.Position, mostRelevantTower.Radius)); + 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; + } + } } - var inTower = Towers.Any(x => x.IsInside(actor) && x.CorrectAmountInside(Module)); - var missingSoakers = !inTower && Towers.Any(x => x.InsufficientAmountInside(Module)); if (forbiddenInverted.Count == 0) { - foreach (var t in Towers.Where(x => x.InsufficientAmountInside(Module) || x.IsInside(actor) && x.CorrectAmountInside(Module))) - forbiddenInverted.Add(ShapeDistance.InvertedCircle(t.Position, t.Radius)); - foreach (var t in Towers.Where(x => x.TooManyInside(Module) || !x.IsInside(actor) && x.CorrectAmountInside(Module))) - forbidden.Add(ShapeDistance.Circle(t.Position, t.Radius)); + 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 (forbidden.Count == 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); + } + 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); } - if (forbidden.Count == 0 || inTower || missingSoakers && forbiddenInverted.Count > 0) - hints.AddForbiddenZone(p => forbiddenInverted.Max(f => f(p)), Towers[0].Activation); - else if (forbidden.Count > 0 && !inTower) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), Towers[0].Activation); } - else if (Towers.Any(x => x.ForbiddenSoakers[slot])) + else { - foreach (var t in Towers) + 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(p => forbidden.Min(f => f(p)), Towers[0].Activation); + } + 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); + } + } + for (var i = 0; i < count; ++i) + { + var t = Towers[i]; + var actors = Module.Raid.WithSlot(); + var acount = actors.Length; + var filteredActors = new List<(int, Actor)>(acount); + + for (var j = 0; j < acount; ++j) + { + var indexActor = actors[j]; + if (!t.ForbiddenSoakers[indexActor.Item1] && indexActor.Item2.Position.InCircle(t.Position, t.Radius)) + { + filteredActors.Add(indexActor); + } + } + + var mask = new BitMask(); + for (var j = 0; j < filteredActors.Count; ++j) + mask[filteredActors[j].Item1] = true; + + hints.PredictedDamage.Add((mask, t.Activation)); } - foreach (var t in Towers) - hints.PredictedDamage.Add((Module.Raid.WithSlot().ExcludedFromMask(t.ForbiddenSoakers).InRadius(t.Position, t.Radius).Mask(), t.Activation)); } } diff --git a/BossMod/Data/ActorEnumeration.cs b/BossMod/Data/ActorEnumeration.cs index 5840312a2e..4ea3b6ce78 100644 --- a/BossMod/Data/ActorEnumeration.cs +++ b/BossMod/Data/ActorEnumeration.cs @@ -185,4 +185,49 @@ public static WPos PositionCentroid(this IEnumerable range) sum /= count; return sum.ToWPos(); } + + public static (int, Actor)[] ExcludedFromMask(this List<(int, Actor)> range, BitMask mask) + { + var count = range.Count; + var result = new List<(int, Actor)>(count); + for (var i = 0; i < count; ++i) + { + var indexActor = range[i]; + if (!mask[indexActor.Item1]) + { + result.Add(indexActor); + } + } + return [.. result]; + } + + public static (int, Actor)[] WhereSlot(this List<(int, Actor)> range, Func predicate) + { + var count = range.Count; + var result = new List<(int, Actor)>(count); + for (var i = 0; i < count; ++i) + { + var indexActor = range[i]; + if (predicate(indexActor.Item1)) + { + result.Add(indexActor); + } + } + return [.. result]; + } + + public static (int, Actor)[] InRadius(this List<(int, Actor)> range, WPos origin, float radius) + { + var count = range.Count; + var result = new List<(int, Actor)>(count); + for (var i = 0; i < count; ++i) + { + var indexActor = range[i]; + if (indexActor.Item2.Position.InCircle(origin, radius)) + { + result.Add(indexActor); + } + } + return [.. result]; + } } diff --git a/BossMod/Data/PartyState.cs b/BossMod/Data/PartyState.cs index 73ae5ac756..53b19ff13a 100644 --- a/BossMod/Data/PartyState.cs +++ b/BossMod/Data/PartyState.cs @@ -49,36 +49,39 @@ void assign(ulong instanceID, Actor? actor) } // select non-null and optionally alive raid members - public IEnumerable WithoutSlot(bool includeDead = false, bool excludeAlliance = false, bool excludeNPCs = false) + public Actor[] WithoutSlot(bool includeDead = false, bool excludeAlliance = false, bool excludeNPCs = false) { - for (var i = 0; i < MaxAllies; ++i) + var limit = excludeNPCs ? MaxAllianceSize : MaxAllies; + var result = new List(limit); + for (var i = 0; i < limit; ++i) { - if (excludeNPCs && i >= MaxAllianceSize) - break; - if (excludeAlliance && i is >= MaxPartySize and < MaxAllianceSize) + if (excludeAlliance && i >= MaxPartySize && i < MaxAllianceSize) continue; + var player = _actors[i]; - if (player == null) - continue; - if (player.IsDead && !includeDead) + if (player == null || !includeDead && player.IsDead) continue; - yield return player; + result.Add(player); } + return [.. result]; } - public IEnumerable<(int, Actor)> WithSlot(bool includeDead = false, bool excludeAlliance = false) + public (int, Actor)[] WithSlot(bool includeDead = false, bool excludeAlliance = false, bool excludeNPCs = false) { - for (var i = 0; i < MaxAllies; ++i) + var limit = excludeNPCs ? MaxAllianceSize : MaxAllies; + var result = new List<(int, Actor)>(limit); + for (var i = 0; i < limit; ++i) { if (excludeAlliance && i is >= MaxPartySize and < MaxAllianceSize) continue; var player = _actors[i]; if (player == null) continue; - if (player.IsDead && !includeDead) + if (!includeDead && player.IsDead) continue; - yield return (i, player); + result.Add((i, player)); } + return [.. result]; } // find a slot index containing specified player (by instance ID); returns -1 if not found diff --git a/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D074GreatestSerpentOfTural.cs b/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D074GreatestSerpentOfTural.cs index e0cc80cc4d..511fb39028 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D074GreatestSerpentOfTural.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D07TenderValley/D074GreatestSerpentOfTural.cs @@ -112,7 +112,7 @@ private static readonly (Square correctTile, Square goalTile)[] tilePairs = [ (new(new(-132, -548), Radius), new(new(-120, -564), Radius)), (new(new(-136, -556), Radius), new(new(-140, -544), Radius))]; - private static readonly List wholeArena = [new Square(center, 12)]; + private static readonly Shape[] wholeArena = [new Square(center, 12)]; private static readonly AOEShapeCustom[] forbiddenShapes = [.. tilePairs.Select(tp => new AOEShapeCustom(wholeArena, [middle, tp.correctTile, tp.goalTile]))]; private static readonly AOEShapeCustom[] safeShapes = [.. tilePairs.Select(tp => new AOEShapeCustom([tp.correctTile, tp.goalTile], InvertForbiddenZone: true))]; diff --git a/BossMod/Modules/Dawntrail/Savage/M03SBruteBomber/TagTeam.cs b/BossMod/Modules/Dawntrail/Savage/M03SBruteBomber/TagTeam.cs index beae3a4956..67e9651738 100644 --- a/BossMod/Modules/Dawntrail/Savage/M03SBruteBomber/TagTeam.cs +++ b/BossMod/Modules/Dawntrail/Savage/M03SBruteBomber/TagTeam.cs @@ -14,10 +14,10 @@ class TagTeamLariatCombo(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_tetherSource[slot] != null && AOEs.Count > 0) + if (_tetherSource[slot] != null && AOEs.Count != 0) { var (safeShapes, dangerShapes) = GetShapesForAOEs(slot); - var coneShapes = cone != null ? [cone] : new List(); + Shape[] coneShapes = cone != null ? [cone] : []; yield return new(new AOEShapeCustom(safeShapes, dangerShapes, coneShapes, true, cone != null ? OperandType.Intersection : OperandType.Union), Arena.Center, default, AOEs.FirstOrDefault().Activation, Colors.SafeFromAOE); @@ -27,7 +27,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) yield return aoe; } - private (List safeShapes, List dangerShapes) GetShapesForAOEs(int slot) + private (Shape[] safeShapes, Shape[] dangerShapes) GetShapesForAOEs(int slot) { List safeShapes = []; List dangerShapes = []; @@ -41,7 +41,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) else dangerShapes.Add(shape); } - return (safeShapes, dangerShapes); + return ([.. safeShapes], [.. dangerShapes]); } public override void AddHints(int slot, Actor actor, TextHints hints) diff --git a/BossMod/Modules/Dawntrail/Savage/M04SWickedThunder/ElectropeEdge.cs b/BossMod/Modules/Dawntrail/Savage/M04SWickedThunder/ElectropeEdge.cs index f5c4f69d2b..3217bee475 100644 --- a/BossMod/Modules/Dawntrail/Savage/M04SWickedThunder/ElectropeEdge.cs +++ b/BossMod/Modules/Dawntrail/Savage/M04SWickedThunder/ElectropeEdge.cs @@ -17,8 +17,10 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) class ElectropeEdgeSpark1(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ElectropeEdgeSpark1), new AOEShapeRect(5, 5, 5)); class ElectropeEdgeSpark2(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ElectropeEdgeSpark2), new AOEShapeRect(15, 15, 15)); -class ElectropeEdgeSidewiseSparkR(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ElectropeEdgeSidewiseSparkR), new AOEShapeCone(60, 90.Degrees())); -class ElectropeEdgeSidewiseSparkL(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ElectropeEdgeSidewiseSparkL), new AOEShapeCone(60, 90.Degrees())); + +abstract class ElectropeEdgeSidewise(BossModule module, AID aid) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCone(60, 90.Degrees())); +class ElectropeEdgeSidewiseSparkR(BossModule module) : ElectropeEdgeSidewise(module, AID.ElectropeEdgeSidewiseSparkR); +class ElectropeEdgeSidewiseSparkL(BossModule module) : ElectropeEdgeSidewise(module, AID.ElectropeEdgeSidewiseSparkL); class ElectropeEdgeStar(BossModule module) : Components.UniformStackSpread(module, 6, 6, alwaysShowSpreads: true) { @@ -31,12 +33,12 @@ public override void OnStatusGain(Actor actor, ActorStatus status) case 0x2F0: // TODO: can target any role, if not during cage?.. var cage = Module.FindComponent(); - var targets = Raid.WithSlot(true); - targets = cage != null ? targets.WhereSlot(i => cage.Order[i] == 2) : targets.WhereActor(p => p.Class.IsSupport()); + var targets = Raid.WithSlot(true, true, true); + targets = cage != null ? [.. targets.WhereSlot(i => cage.Order[i] == 2)] : [.. targets.WhereActor(p => p.Class.IsSupport())]; AddStacks(targets.Actors(), status.ExpireAt.AddSeconds(1)); break; case 0x2F1: - AddSpreads(Raid.WithoutSlot(true), status.ExpireAt.AddSeconds(1)); + AddSpreads(Raid.WithoutSlot(true, true, true), status.ExpireAt.AddSeconds(1)); break; } } diff --git a/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/ChillingCataclysm.cs b/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/ChillingCataclysm.cs index de973424f4..e739dd9d26 100644 --- a/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/ChillingCataclysm.cs +++ b/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/ChillingCataclysm.cs @@ -12,8 +12,9 @@ public override void OnActorCreated(Actor actor) { if ((OID)actor.OID == OID.ArcaneSphere2) { - AOEs.Add(new(_shape, actor.Position, -0.003f.Degrees(), WorldState.FutureTime(7.1f))); - AOEs.Add(new(_shape, actor.Position, 44.998f.Degrees(), WorldState.FutureTime(7.1f))); + var activation = WorldState.FutureTime(7.1f); + AOEs.Add(new(_shape, actor.Position, -0.003f.Degrees(), activation)); + AOEs.Add(new(_shape, actor.Position, 44.998f.Degrees(), activation)); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D061CausticGrebuloff.cs b/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D061CausticGrebuloff.cs index bb49aa1549..523afde16f 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D061CausticGrebuloff.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D06DeadEnds/D061CausticGrebuloff.cs @@ -193,7 +193,7 @@ class WaveOfNausea(BossModule module) : Components.GenericAOEs(module) { private readonly List _aoes = []; private static readonly AOEShapeDonut donut = new(6, 40); - private static readonly List differenceShapes = [new Circle(new(271.5f, -178), 6), new Circle(new(261.5f, -178), 6)]; + private static readonly Shape[] differenceShapes = [new Circle(new(271.5f, -178), 6), new Circle(new(261.5f, -178), 6)]; public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; diff --git a/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VoidMeteor.cs b/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VoidMeteor.cs index a584b7c0c9..0c5260e164 100644 --- a/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VoidMeteor.cs +++ b/BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VoidMeteor.cs @@ -51,7 +51,7 @@ public override void DrawArenaBackground(int pcSlot, Actor pc) { polygons.Add(new PolygonCustom(BuildShadowPolygon(source.Position - Arena.Center, _meteors[i] - Arena.Center, Arena.Bounds.MaxApproxError))); } - _aoe = new(new AOEShapeCustom(polygons), Arena.Center); + _aoe = new(new AOEShapeCustom([.. polygons]), Arena.Center); } } base.DrawArenaBackground(pcSlot, pc); diff --git a/BossMod/Modules/Endwalker/Variant/V02MR/V024Shishio/FocusedTremor.cs b/BossMod/Modules/Endwalker/Variant/V02MR/V024Shishio/FocusedTremor.cs index a4bbd7198a..e769e2ab2b 100644 --- a/BossMod/Modules/Endwalker/Variant/V02MR/V024Shishio/FocusedTremor.cs +++ b/BossMod/Modules/Endwalker/Variant/V02MR/V024Shishio/FocusedTremor.cs @@ -19,12 +19,12 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) var sixFulmsUnderStatus = actor.FindStatus(SID.SixFulmsUnder); var expireAt = sixFulmsUnderStatus?.ExpireAt ?? DateTime.MaxValue; var extra = circle != null ? 10 : 0; - List rectShape = [new RectangleSE(_aoe.Origin, _aoe.Origin + (rect.LengthFront + extra) * _aoe.Rotation.ToDirection(), rect.HalfWidth)]; - var circleShape = circle != null ? [circle] : new List(); + RectangleSE[] rectShape = [new(_aoe.Origin, _aoe.Origin + (rect.LengthFront + extra) * _aoe.Rotation.ToDirection(), rect.HalfWidth)]; + Circle[] circleShape = circle != null ? [circle] : []; var aoeInstance = _aoe with { Origin = Arena.Center, - Shape = new AOEShapeCustom(rectShape, [], circleShape, false, circle != null ? OperandType.Xor : OperandType.Union) with { InvertForbiddenZone = isCasterActive }, + Shape = new AOEShapeCustom(rectShape, null, circleShape, false, circle != null ? OperandType.Xor : OperandType.Union) with { InvertForbiddenZone = isCasterActive }, Color = isCasterActive ? Colors.SafeFromAOE : Colors.AOE, Activation = !isCasterActive ? expireAt : firstAOEActivation }; diff --git a/BossMod/Modules/Endwalker/Variant/V02MR/V026ShishuChochin/V026ShishuChochin.cs b/BossMod/Modules/Endwalker/Variant/V02MR/V026ShishuChochin/V026ShishuChochin.cs index a0edbd4614..bf75b6a89f 100644 --- a/BossMod/Modules/Endwalker/Variant/V02MR/V026ShishuChochin/V026ShishuChochin.cs +++ b/BossMod/Modules/Endwalker/Variant/V02MR/V026ShishuChochin/V026ShishuChochin.cs @@ -21,7 +21,7 @@ class Lanterns(BossModule module) : Components.GenericAOEs(module) private readonly List lanterns = [lantern1, lantern2, lantern3]; public override IEnumerable ActiveAOEs(int slot, Actor actor) { - yield return new(new AOEShapeCustom(lanterns, InvertForbiddenZone: true), Arena.Center, Color: Colors.SafeFromAOE); + yield return new(new AOEShapeCustom([.. lanterns], InvertForbiddenZone: true), Arena.Center, Color: Colors.SafeFromAOE); } public override void OnEventEnvControl(byte index, uint state) diff --git a/BossMod/Modules/Global/MaskedCarnivale/Stage32AGoldenOpportunity/Stage32Act2.cs b/BossMod/Modules/Global/MaskedCarnivale/Stage32AGoldenOpportunity/Stage32Act2.cs index 0f7dad1b52..75b23f8e88 100644 --- a/BossMod/Modules/Global/MaskedCarnivale/Stage32AGoldenOpportunity/Stage32Act2.cs +++ b/BossMod/Modules/Global/MaskedCarnivale/Stage32AGoldenOpportunity/Stage32Act2.cs @@ -69,8 +69,14 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) cones.Add(new(caster.Position, cone.Radius, spell.Rotation, cone.HalfAngle, 2)); if (cones.Count == 4) { - AOEShapeCustom intersect = new([cones[0]], Shapes2: cones.Skip(1), Operand: OperandType.Intersection); - AOEShapeCustom xor = new([cones[0]], Shapes2: cones.Skip(1), Operand: OperandType.Xor); + var coneskip1 = new List(3); + for (var i = 1; i < 4; ++i) + { + coneskip1.Add(cones[i]); + } + ConeV[] conesA = [.. coneskip1]; + AOEShapeCustom intersect = new([cones[0]], Shapes2: conesA, Operand: OperandType.Intersection); + AOEShapeCustom xor = new([cones[0]], Shapes2: conesA, Operand: OperandType.Xor); var clipper = new PolygonClipper(); var combinedShapes = clipper.Union(new PolygonClipper.Operand(intersect.GetCombinedPolygon(Arena.Center)), new PolygonClipper.Operand(xor.GetCombinedPolygon(Arena.Center))); diff --git a/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D112Ziggy.cs b/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D112Ziggy.cs index 2928b91837..fca7ac67c6 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D112Ziggy.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D112Ziggy.cs @@ -67,7 +67,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) { cones.Add(new(D112Ziggy.ArenaCenter, 11.1f, 20, Angle.FromDirection(Module.PrimaryActor.DirectionTo(c)), halfAngle)); } - yield return new(new AOEShapeCustom(cones, InvertForbiddenZone: true), Arena.Center, Color: Colors.SafeFromAOE); + yield return new(new AOEShapeCustom([.. cones], InvertForbiddenZone: true), Arena.Center, Color: Colors.SafeFromAOE); } } diff --git a/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs b/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs index 64f025db85..fedbf675d5 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D13SohrKhai/D132Poqhiraj.cs @@ -138,7 +138,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) var count = _kb.safeWalls.Count; if (count is 0 or 8) return; - List rects = []; + List rects = new(count); for (var i = 0; i < count; ++i) { var safeWall = _kb.safeWalls[i].Vertex1; @@ -146,7 +146,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) var pos = new WPos(safeWall.X, safeWall.Z + 5); rects.Add(new(pos + dir, pos - 3.5f * dir, 5)); } - AOEShapeCustom aoe = new(rects, InvertForbiddenZone: true); + AOEShapeCustom aoe = new([.. rects], InvertForbiddenZone: true); _aoe = new(aoe, Arena.Center, default, Module.CastFinishAt(spell), Colors.SafeFromAOE, true); } } diff --git a/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs b/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs index 826d5315a6..dc42e4adf8 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs @@ -141,7 +141,7 @@ private void GenerateHints() if (!Module.Enemies(OID.Whirlwind).Any(x => x.Position.InCone(D152DotoliCiloc.ArenaCenter, deg, angle))) cones.Add(new(D152DotoliCiloc.ArenaCenter, 20, deg, angle)); } - _aoe = new(new AOEShapeCustom(cones, InvertForbiddenZone: true), D152DotoliCiloc.ArenaCenter, default, activation, Colors.SafeFromAOE); + _aoe = new(new AOEShapeCustom([.. cones], InvertForbiddenZone: true), D152DotoliCiloc.ArenaCenter, default, activation, Colors.SafeFromAOE); } public override void OnActorCreated(Actor actor) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D111SpectralThief.cs b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D111SpectralThief.cs index 1dc2423f78..aeca0c735a 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D111SpectralThief.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D111SpectralThief.cs @@ -2,7 +2,6 @@ namespace BossMod.Shadowbringers.Dungeon.D11HeroesGauntlet.D111SpectralThief; public enum OID : uint { - Boss = 0x2DEC, // R0.875 SpectralThief = 0x2DED, // R0.875 Marker = 0x1EAED9, diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs index bad339d7e7..e17de98463 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs @@ -164,7 +164,7 @@ class CratersWildRampage(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { if (Circles.Count != 0) - yield return new(new AOEShapeCustom(Circles) with { InvertForbiddenZone = invert }, Arena.Center, default, activation, invert ? Colors.SafeFromAOE : Colors.AOE); + yield return new(new AOEShapeCustom([.. Circles]) with { InvertForbiddenZone = invert }, Arena.Center, default, activation, invert ? Colors.SafeFromAOE : Colors.AOE); } public override void OnActorEAnim(Actor actor, uint state) diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/Border.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/Border.cs index cb50e17031..4f8dc67f99 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/Border.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/Border.cs @@ -55,7 +55,7 @@ private static List RingBorder(Angle centerOffset, float ringRadius, bool private static List MidDanger() { - var outerRing = RingBorder(0.Degrees(), _outerRingRadius, true); + var outerRing = RingBorder(default, _outerRingRadius, true); var innerRing = RingBorder(22.5f.Degrees(), _innerRingRadius, false); innerRing.Reverse(); outerRing.AddRange(innerRing); @@ -66,7 +66,7 @@ private static List OutDanger() { var outerBoundary = CurveApprox.Rect(BoundsCenter, new(0, 1), 34.5f, 34.5f).ToList(); // using a square instead of circle to have less vertices. polygon will get clipped with circle border anyway outerBoundary.Add(outerBoundary[0]); - var innerRing = RingBorder(0.Degrees(), _outerRingRadius, false); + var innerRing = RingBorder(default, _outerRingRadius, false); outerBoundary.AddRange(innerRing); return outerBoundary; } diff --git a/BossMod/Modules/Stormblood/Dungeon/D02ShisuiOfTheVioletTides/D022RubyPrincess.cs b/BossMod/Modules/Stormblood/Dungeon/D02ShisuiOfTheVioletTides/D022RubyPrincess.cs index e6b0fbe419..cc8016dc6b 100644 --- a/BossMod/Modules/Stormblood/Dungeon/D02ShisuiOfTheVioletTides/D022RubyPrincess.cs +++ b/BossMod/Modules/Stormblood/Dungeon/D02ShisuiOfTheVioletTides/D022RubyPrincess.cs @@ -45,7 +45,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) { foreach (var c in openChests) yield return new(circle, c.Center); - yield return new(new AOEShapeCustom(closedChests) with { InvertForbiddenZone = !IsOld(actor) && active }, Arena.Center, Color: IsOld(actor) || !active ? Colors.AOE : Colors.SafeFromAOE); + yield return new(new AOEShapeCustom([.. closedChests]) with { InvertForbiddenZone = !IsOld(actor) && active }, Arena.Center, Color: IsOld(actor) || !active ? Colors.AOE : Colors.SafeFromAOE); } public override void Update() diff --git a/BossMod/Modules/Stormblood/Dungeon/D13Burn/D131Hedetet.cs b/BossMod/Modules/Stormblood/Dungeon/D13Burn/D131Hedetet.cs index 37b1d98d1e..48b786c958 100644 --- a/BossMod/Modules/Stormblood/Dungeon/D13Burn/D131Hedetet.cs +++ b/BossMod/Modules/Stormblood/Dungeon/D13Burn/D131Hedetet.cs @@ -67,9 +67,10 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) var dir = boss.DirectionTo(c); rects.Add(new(c.Position + 0.1f * dir, c.Position + Length * dir, 1.6f)); } + RectangleSE[] rectsA = [.. rects]; yield return _target == actor - ? new(new AOEShapeCustom(rects, InvertForbiddenZone: true), Arena.Center, Color: Colors.SafeFromAOE) - : new(new AOEShapeCustom([new RectangleSE(boss.Position, boss.Position + Length * boss.DirectionTo(_target), 2)], rects), Arena.Center); + ? new(new AOEShapeCustom([.. rects], InvertForbiddenZone: true), Arena.Center, Color: Colors.SafeFromAOE) + : new(new AOEShapeCustom([new RectangleSE(boss.Position, boss.Position + Length * boss.DirectionTo(_target), 2)], rectsA), Arena.Center); } }