diff --git a/BossMod/BossModule/Shapes.cs b/BossMod/BossModule/Shapes.cs index f593b42517..6b837e5c6b 100644 --- a/BossMod/BossModule/Shapes.cs +++ b/BossMod/BossModule/Shapes.cs @@ -7,9 +7,11 @@ public abstract record class Shape public const float MaxApproxError = 0.01f; public abstract List Contour(WPos center); - public abstract RelSimplifiedComplexPolygon ToPolygon(WPos center); public abstract string ComputeHash(); + public RelSimplifiedComplexPolygon ToPolygon(WPos center) + => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new(Contour(center))])); + protected List GetOrCreateContour(WPos center, Func> createContour) { var key = (ComputeHash(), center); @@ -46,8 +48,6 @@ public override List Contour(WPos center) => GetOrCreateContour(center, () => CurveApprox.Circle(Radius, MaxApproxError).Select(p => p + (Center - center)).ToList()); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); public override Func Distance() => ShapeDistance.Circle(Center, Radius); public override string ComputeHash() => ComputeSHA512($"{nameof(Circle)}:{Center.X},{Center.Z},{Radius}"); @@ -61,9 +61,6 @@ public record class PolygonCustom(IEnumerable Vertices) : Shape public override List Contour(WPos center) => GetOrCreateContour(center, () => Vertices.Select(v => v - center).ToList()); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - private bool IsConvex() { var hash = ComputeHash() + "IsConvex"; @@ -109,11 +106,7 @@ private bool IsCounterClockwise() return isCounterClockwise; } - public override Func Distance() - { - return IsConvex() ? IsCounterClockwise() ? ShapeDistance.ConvexPolygon(Vertices, false) : ShapeDistance.ConvexPolygon(Vertices, true) - : ShapeDistance.ConcavePolygon(Vertices); - } + public override Func Distance() => IsConvex() ? ShapeDistance.ConvexPolygon(Vertices, !IsCounterClockwise()) : ShapeDistance.ConcavePolygon(Vertices); public override string ComputeHash() { @@ -128,9 +121,6 @@ public override List Contour(WPos center) => GetOrCreateContour(center, () => CurveApprox.Donut(InnerRadius, OuterRadius, MaxApproxError).Select(p => p + (Center - center)).ToList()); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - public override Func Distance() => ShapeDistance.Donut(Center, InnerRadius, OuterRadius); @@ -154,9 +144,6 @@ public override List Contour(WPos center) ]; }); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - public override Func Distance() => ShapeDistance.Rect(Center, Rotation, HalfHeight, HalfHeight, HalfWidth); @@ -201,174 +188,31 @@ public override List Contour(WPos center) ]; }); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - public override Func Distance() => ShapeDistance.Cross(Center, Rotation, Length, HalfWidth); public override string ComputeHash() => ComputeSHA512($"{nameof(Cross)}:{Center.X},{Center.Z},{Length},{HalfWidth},{Rotation.Rad}"); } -// Equilateral triangle defined by center, radius and rotation -public record class TriangleE(WPos Center, float Radius, Angle Rotation = default) : Shape -{ - public override List Contour(WPos center) - => GetOrCreateContour(center, () => - { - var sqrt3 = MathF.Sqrt(3); - var halfSide = Radius; - var height = halfSide * sqrt3; - var centerTriangle = Center + new WDir(0, height / 3); - var vertices = new List - { - centerTriangle + new WDir(-halfSide, height / 3) - center, - centerTriangle + new WDir(halfSide, height / 3) - center, - centerTriangle + new WDir(0, -2 * height / 3) - center - }; - - var cos = MathF.Cos(Rotation.Rad); - var sin = MathF.Sin(Rotation.Rad); - return vertices.Select(v => new WDir(v.X * cos - v.Z * sin, v.X * sin + v.Z * cos)).ToList(); - }); - - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - - public override Func Distance() - { - var sqrt3 = MathF.Sqrt(3); - var halfSide = Radius; - var height = halfSide * sqrt3; - var a = new WDir(-halfSide, height / 3); - var b = new WDir(halfSide, height / 3); - var c = new WDir(0, -2 * height / 3); - - var cos = MathF.Cos(Rotation.Rad); - var sin = MathF.Sin(Rotation.Rad); - - var rotatedA = new WDir(a.X * cos - a.Z * sin, a.X * sin + a.Z * cos); - var rotatedB = new WDir(b.X * cos - b.Z * sin, b.X * sin + b.Z * cos); - var rotatedC = new WDir(c.X * cos - c.Z * sin, c.X * sin + c.Z * cos); - - var relTriangle = new RelTriangle(rotatedA, rotatedB, rotatedC); - - return ShapeDistance.Tri(Center, relTriangle); - } - - public override string ComputeHash() => ComputeSHA512($"{nameof(TriangleE)}:{Center.X},{Center.Z},{Radius},{Rotation.Rad}"); -} - -// custom Triangle defined by three sides and rotation, mind the triangle inequality, side1 + side2 >= side3 -public record class TriangleS(WPos Center, float SideA, float SideB, float SideC, Angle Rotation = default) : Shape -{ - public override List Contour(WPos center) - => GetOrCreateContour(center, () => - { - var sides = new[] { SideA, SideB, SideC }.OrderByDescending(s => s).ToArray(); - var a = sides[0]; - var b = sides[1]; - var c = sides[2]; - var vertex1 = new WPos(0, 0); - var vertex2 = new WPos(a, 0); - var cosC = (b * b + a * a - c * c) / (2 * a * b); - var sinC = MathF.Sqrt(1 - cosC * cosC); - var vertex3 = new WPos(b * cosC, b * sinC); - var centroid = new WPos((vertex1.X + vertex2.X + vertex3.X) / 3, (vertex1.Z + vertex2.Z + vertex3.Z) / 3); - - var vertices = new List - { - new(vertex3.X - centroid.X, vertex3.Z - centroid.Z), - new(vertex2.X - centroid.X, vertex2.Z - centroid.Z), - new(vertex1.X - centroid.X, vertex1.Z - centroid.Z) - }; - - var cos = MathF.Cos(Rotation.Rad); - var sin = MathF.Sin(Rotation.Rad); - return vertices.Select(v => new WDir(v.X * cos - v.Z * sin, v.X * sin + v.Z * cos)).ToList(); - }); - - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - - public override Func Distance() - { - var sides = new[] { SideA, SideB, SideC }.OrderByDescending(s => s).ToArray(); - var a = sides[0]; - var b = sides[1]; - var c = sides[2]; - var vertex1 = new WDir(0, 0); - var vertex2 = new WDir(a, 0); - var cosC = (b * b + a * a - c * c) / (2 * a * b); - var sinC = MathF.Sqrt(1 - cosC * cosC); - var vertex3 = new WDir(b * cosC, b * sinC); - - var cos = MathF.Cos(Rotation.Rad); - var sin = MathF.Sin(Rotation.Rad); - - var rotatedVertex1 = new WDir(vertex1.X * cos - vertex1.Z * sin, vertex1.X * sin + vertex1.Z * cos); - var rotatedVertex2 = new WDir(vertex2.X * cos - vertex2.Z * sin, vertex2.X * sin + vertex2.Z * cos); - var rotatedVertex3 = new WDir(vertex3.X * cos - vertex3.Z * sin, vertex3.X * sin + vertex3.Z * cos); - - var relTriangle = new RelTriangle(rotatedVertex1, rotatedVertex2, rotatedVertex3); - - return ShapeDistance.Tri(Center, relTriangle); - } - - public override string ComputeHash() => ComputeSHA512($"{nameof(TriangleS)}:{Center.X},{Center.Z},{SideA},{SideB},{SideC},{Rotation.Rad}"); -} +// Equilateral triangle defined by center, sidelength and rotation +public record class TriangleE(WPos Center, float SideLength, Angle Rotation = default) : Shape -// Triangle definded by base length and angle at the apex, apex points north by default -public record class TriangleA(WPos Center, float BaseLength, Angle ApexAngle, Angle Rotation = default) : Shape { + private static readonly float heightFactor = MathF.Sqrt(3) / 2; public override List Contour(WPos center) => GetOrCreateContour(center, () => { - var apexAngleRad = ApexAngle.Rad; - var height = BaseLength / 2 / MathF.Tan(apexAngleRad / 2); - var halfBase = BaseLength / 2; - var vertex1 = new WPos(-halfBase, 0); - var vertex2 = new WPos(halfBase, 0); - var vertex3 = new WPos(0, -height); - var centroid = new WPos((vertex1.X + vertex2.X + vertex3.X) / 3, (vertex1.Z + vertex2.Z + vertex3.Z) / 3); - + var height = SideLength * heightFactor; + var vertices = new List { new(SideLength / 2, height / 2), new(-SideLength / 2, height / 2), new(0, -height / 2) }; var cos = MathF.Cos(Rotation.Rad); var sin = MathF.Sin(Rotation.Rad); - var vertices = new List - { - new(vertex1.X - centroid.X, vertex1.Z - centroid.Z), - new(vertex2.X - centroid.X, vertex2.Z - centroid.Z), - new(vertex3.X - centroid.X, vertex3.Z - centroid.Z) - }; - - return vertices.Select(v => new WDir(v.X * cos - v.Z * sin, v.X * sin + v.Z * cos)).ToList(); + vertices = vertices.Select(v => new WDir(v.X * cos - v.Z * sin, v.X * sin + v.Z * cos)).ToList(); + return vertices.Select(v => v + (Center - center)).ToList(); }); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - - public override Func Distance() - { - var apexAngleRad = ApexAngle.Rad; - var height = BaseLength / 2 / MathF.Tan(apexAngleRad / 2); - var halfBase = BaseLength / 2; - var vertex1 = new WDir(-halfBase, 0); - var vertex2 = new WDir(halfBase, 0); - var vertex3 = new WDir(0, -height); - - var cos = MathF.Cos(Rotation.Rad); - var sin = MathF.Sin(Rotation.Rad); - - var rotatedVertex1 = new WDir(vertex1.X * cos - vertex1.Z * sin, vertex1.X * sin + vertex1.Z * cos); - var rotatedVertex2 = new WDir(vertex2.X * cos - vertex2.Z * sin, vertex2.X * sin + vertex2.Z * cos); - var rotatedVertex3 = new WDir(vertex3.X * cos - vertex3.Z * sin, vertex3.X * sin + vertex3.Z * cos); + public override Func Distance() => ShapeDistance.ConvexPolygon(Contour(Center).Select(dir => dir + Center), cw: true); - var relTriangle = new RelTriangle(rotatedVertex1, rotatedVertex2, rotatedVertex3); - - return ShapeDistance.Tri(Center, relTriangle); - } - - public override string ComputeHash() => ComputeSHA512($"{nameof(TriangleA)}:{Center.X},{Center.Z},{BaseLength},{ApexAngle.Rad},{Rotation.Rad}"); + public override string ComputeHash() => ComputeSHA512($"{nameof(TriangleE)}:{Center.X},{Center.Z},{SideLength},{Rotation.Rad}"); } // for polygons defined by a radius and n amount of vertices @@ -390,11 +234,7 @@ public override List Contour(WPos center) return vertices; }); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - - public override Func Distance() - => ShapeDistance.ConvexPolygon(Contour(Center).Select(dir => dir + Center), cw: true); + public override Func Distance() => ShapeDistance.ConvexPolygon(Contour(Center).Select(dir => dir + Center), cw: true); public override string ComputeHash() => ComputeSHA512($"{nameof(Polygon)}:{Center.X},{Center.Z},{Radius},{Vertices},{Rotation.Rad}"); } @@ -406,9 +246,6 @@ public override List Contour(WPos center) => GetOrCreateContour(center, () => CurveApprox.CircleSector(Center, Radius, StartAngle, EndAngle, MaxApproxError).Select(p => p - center).ToList()); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - public override Func Distance() => ShapeDistance.Cone(Center, Radius, (StartAngle + EndAngle) / 2, (EndAngle - StartAngle) / 2); @@ -425,9 +262,6 @@ public override List Contour(WPos center) => GetOrCreateContour(center, () => CurveApprox.DonutSector(InnerRadius, OuterRadius, StartAngle, EndAngle, MaxApproxError).Select(p => p + (Center - center)).ToList()); - public override RelSimplifiedComplexPolygon ToPolygon(WPos center) - => GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))])); - public override Func Distance() => ShapeDistance.DonutSector(Center, InnerRadius, OuterRadius, (StartAngle + EndAngle) / 2, (EndAngle - StartAngle) / 2); diff --git a/BossMod/Components/StackSpread.cs b/BossMod/Components/StackSpread.cs index 5cae89fe2b..59a0d0e0b1 100644 --- a/BossMod/Components/StackSpread.cs +++ b/BossMod/Components/StackSpread.cs @@ -316,13 +316,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)) forbiddenInverted.Add(ShapeDistance.InvertedRect(b.Source.Position, (b.Target.Position - b.Source.Position).Normalized(), Range, 0, HalfWidth)); - // prevent overlapping if there are multiple line stacks - if (isBaitNotTarget && isBaitTarget) + // prevent overlapping if there are multiple line stacks, or if an actor is forbidden to enter + if (isBaitNotTarget && isBaitTarget || forbiddenActors) foreach (var b in ActiveBaits.Where(x => x.Target != actor)) forbidden.Add(ShapeDistance.Rect(b.Source.Position, (b.Target.Position - b.Source.Position).Normalized(), Range, 0, 2 * HalfWidth)); - if (forbiddenActors) // if too many people are dead, you can become a forbidden actor and get stack at the same time - foreach (var b in ActiveBaits.Where(x => x.Target != actor)) - forbidden.Add(ShapeDistance.Rect(b.Source.Position, (b.Target.Position - b.Source.Position).Normalized(), Range, 0, HalfWidth)); if (forbiddenInverted.Count > 0) hints.AddForbiddenZone(p => forbiddenInverted.Select(f => f(p)).Max(), ActiveBaits.FirstOrDefault().Activation); if (forbidden.Count > 0) diff --git a/BossMod/Modules/Endwalker/Alliance/A13Azeyma/WildfireWard.cs b/BossMod/Modules/Endwalker/Alliance/A13Azeyma/WildfireWard.cs index 2cbc4f41bb..c3d8b982f5 100644 --- a/BossMod/Modules/Endwalker/Alliance/A13Azeyma/WildfireWard.cs +++ b/BossMod/Modules/Endwalker/Alliance/A13Azeyma/WildfireWard.cs @@ -3,8 +3,10 @@ class WildfireWard(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.IlluminatingGlimpse), 15, false, 1, kind: Kind.DirLeft); class ArenaBounds(BossModule module) : Components.GenericAOEs(module) { - private static readonly Shape triangle = new PolygonCustom([new(-761.5f, -743.38f), new(-750, -763.27f), new(-738.51f, -743.39f)]); - private static readonly Shape circle = new Circle(A13Azeyma.NormalCenter, 29.5f); + + private static readonly Circle circle = new(A13Azeyma.NormalCenter, 29.5f); + private static readonly WPos triangleCenter = new(-750, -753.325f); + private static readonly TriangleE triangle = new(triangleCenter, 24); private static readonly AOEShapeCustom triangleCutOut = new([circle], [triangle]); private static readonly ArenaBoundsComplex TriangleBounds = new([triangle]); @@ -22,7 +24,7 @@ public override void OnEventEnvControl(byte index, uint state) { _aoe = null; Arena.Bounds = TriangleBounds; - Arena.Center = TriangleBounds.Center; + Arena.Center = triangleCenter; } else if (state == 0x00080004) Arena.Bounds = A13Azeyma.NormalBounds; diff --git a/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21Nophica.cs b/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21Nophica.cs index fb0bfc05d9..74c777600e 100644 --- a/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21Nophica.cs +++ b/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21Nophica.cs @@ -1,14 +1,24 @@ namespace BossMod.Endwalker.Alliance.A21Nophica; -class ArenaBounds(BossModule module) : BossComponent(module) +class ArenaBounds(BossModule module) : Components.GenericAOEs(module) { + private static readonly AOEShapeDonut donut = new(28, 34); + private AOEInstance? _aoe; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + public override void OnEventEnvControl(byte index, uint state) { if (index == 0x39) { if (state == 0x02000200) + _aoe = new(donut, Module.Center, default, WorldState.FutureTime(5.8f)); + if (state is 0x00200010 or 0x00020001) + { Arena.Bounds = A21Nophica.SmallerBounds; - if (state == 0x00400004) + _aoe = null; + } + if (state is 0x00080004 or 0x00400004) Arena.Bounds = A21Nophica.DefaultBounds; } } diff --git a/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21NophicaStates.cs b/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21NophicaStates.cs index 595132d68d..4cd39fd2e1 100644 --- a/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21NophicaStates.cs +++ b/BossMod/Modules/Endwalker/Alliance/A21Nophica/A21NophicaStates.cs @@ -22,6 +22,7 @@ private void SinglePhase(uint id) HeavensEarth(id + 0x90000, 3.1f); Abundance(id + 0xA0000, 6.3f); Abundance(id + 0xB0000, 4.2f); + MatronsPlentyFloralHazeReapersGaleLandwaker(id + 0xC0000, 11.2f); // TODO: verify. even on a MINE run i only saw the start of this SimpleState(id + 0xFF0000, 100, "???"); } diff --git a/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/A22AlthykNymeiaStates.cs b/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/A22AlthykNymeiaStates.cs index 3685e34c2a..8fec630814 100644 --- a/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/A22AlthykNymeiaStates.cs +++ b/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/A22AlthykNymeiaStates.cs @@ -207,7 +207,8 @@ private void HydrostasisPetrai(uint id, float delay) .ActivateOnEnter() .DeactivateOnExit() .SetHint(StateMachine.StateHint.Tankbuster); - ComponentCondition(id + 0x20, 15, comp => comp.NumCasts >= 1, "Knockback 1"); + ComponentCondition(id + 0x20, 15, comp => comp.NumCasts >= 1, "Knockback 1") + .SetHint(StateMachine.StateHint.Knockback); ComponentCondition(id + 0x21, 3, comp => comp.NumCasts >= 2, "Knockback 2"); ComponentCondition(id + 0x22, 3, comp => comp.NumCasts >= 3, "Knockback 3") .DeactivateOnExit(); @@ -219,7 +220,8 @@ private void HydrostasisTimeAndTide(uint id, float delay) ComponentCondition(id + 0x10, 2.0f, comp => comp.Active) .ActivateOnEnter(); ActorCast(id + 0x20, _module.Althyk, AID.TimeAndTide, 0.1f, 10); // TODO: boss often dies here... - ComponentCondition(id + 0x30, 2, comp => comp.NumCasts >= 1, "Knockback 1"); + ComponentCondition(id + 0x30, 2, comp => comp.NumCasts >= 1, "Knockback 1") + .SetHint(StateMachine.StateHint.Knockback); ComponentCondition(id + 0x31, 3, comp => comp.NumCasts >= 2, "Knockback 2"); ComponentCondition(id + 0x32, 3, comp => comp.NumCasts >= 3, "Knockback 3") .DeactivateOnExit(); diff --git a/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/Axioma.cs b/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/Axioma.cs index f49ac5bcbd..da438d56f9 100644 --- a/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/Axioma.cs +++ b/BossMod/Modules/Endwalker/Alliance/A22AlthykNymeia/Axioma.cs @@ -1,27 +1,56 @@ namespace BossMod.Endwalker.Alliance.A22AlthykNymeia; -// TODO: show zone shapes -class Axioma(BossModule module) : BossComponent(module) +class Axioma(BossModule module) : Components.GenericAOEs(module) { + private const string riskHint = "GTFO from rifts!"; + private const string risk2Hint = "Walk into a rift!"; + private const string stayHint = "Stay inside rift!"; public bool ShouldBeInZone { get; private set; } - private readonly BitMask _inZone; + private bool active; - public override void AddHints(int slot, Actor actor, TextHints hints) - { - if (_inZone[slot] != ShouldBeInZone) - hints.Add(ShouldBeInZone ? "Go to dark zone!" : "GTFO from dark zone!"); - } - - public override void OnStatusGain(Actor actor, ActorStatus status) + private static readonly PolygonCustom shapeCustom1 = new([new(35.65f, -725), new(35.3f, -726.57f), new(35.25f, -726.83f), new(34.42f, -728.66f), + new(33.38f, -730.2f), new(32.17f, -731.72f), new(30.67f, -733.08f), new(28.83f, -734.21f), new(27.19f, -734.83f), new(25, -735.44f), new(25, -725)]); + private static readonly PolygonCustom shapeCustom2 = new([new(75, -725), new(75, -732.32f), new(73.49f, -732.17f), new(72.1f, -731.97f), + new(70.54f, -731.49f), new(68.88f, -731.16f), new(67.34f, -731.01f), new(66.02f, -731.01f), new(64.64f, -730.92f), new(63.15f, -730.55f), + new(61.64f, -729.96f), new(60.4f, -729.34f), new(59.45f, -728.39f), new(58.74f, -727.34f), new(58.32f, -726.08f), new(58.1f, -725)]); + private static readonly PolygonCustom shapeCustom3 = new([new(64.10f, -775), new(59.2f, -775), new(58.54f, -773.85f), new(57.72f, -771.66f), + new(57.24f, -769.38f), new(57.03f, -767.12f), new(57.24f, -765), new(57.81f, -762.68f), new(58.56f, -760.89f), new(59.61f, -759.09f), + new(61.06f, -757.38f), new(62.99f, -755.92f), new(65.16f, -754.63f), new(67.51f, -754.09f), new(69.88f, -753.99f), new(72.09f, -754.32f), + new(73.44f, -754.63f), new(74.57f, -755.01f), new(75, -755.14f), new(75, -759.46f), new(73.47f, -758.86f), new(73.47f, -758.87f), + new(73.16f, -758.75f), new(71.17f, -758.22f), new(69.58f, -757.95f), new(68.02f, -758.06f), new(66.52f, -758.39f), new(65.11f, -759.16f), + new(63.96f, -760.15f), new(62.89f, -761.36f), new(62.13f, -762.68f), new(61.59f, -764.06f), new(61.19f, -765.64f), new(61.04f, -767.2f), + new(61.26f, -769.04f), new(61.6f, -770.57f), new(62.21f, -772.18f), new(62.94f, -773.45f), new(63.75f, -774.56f)]); + private static readonly PolygonCustom shapeCustom4 = new([new(42.61f, -775), new(37.38f, -775), new(36.4f, -773.45f), new(35.41f, -771.43f), + new(34.71f, -769.39f), new(34.37f, -767.23f), new(34.28f, -765.23f), new(34.37f, -763.22f), new(34.56f, -761.38f), new(34.72f, -759.54f), + new(34.95f, -757.71f), new(35.02f, -757.22f), new(39.27f, -755.75f), new(38.92f, -758.26f), new(38.72f, -760.02f), new(38.57f, -761.76f), + new(38.39f, -763.48f), new(38.28f, -765.1f), new(38.36f, -766.71f), new(38.57f, -768.33f), new(39.13f, -769.86f), new(39.8f, -771.33f), + new(40.73f, -772.79f), new(41.23f, -773.43f), new(41.81f, -774.16f)]); + private static readonly PolygonCustom shapeCustom5 = new([new(25, -757.62f), new(25, -753.6f), new(26.54f, -753.73f), new(29.55f, -754.03f), + new(31.73f, -754.02f), new(33.74f, -753.66f), new(35.83f, -753.04f), new(37.51f, -752.18f), new(38.8f, -751.22f), new(39.97f, -749.94f), + new(40.91f, -748.65f), new(41.76f, -747.09f), new(42.45f, -745.39f), new(42.99f, -743.66f), new(43.4f, -741.94f), new(43.61f, -740.1f), + new(43.93f, -737.84f), new(44.1f, -735.59f), new(44.17f, -733.28f), new(44.18f, -730.96f), new(44.21f, -728.77f), new(44.2f, -726.58f), + new(44.17f, -725), new(48.16f, -725), new(48.2f, -726.57f), new(48.21f, -728.76f), new(48.22f, -730.96f), new(48.21f, -733.25f), + new(48.13f, -735.67f), new(48.08f, -736.37f), new(47.96f, -738.06f), new(47.69f, -740.49f), new(47.39f, -742.41f), new(46.94f, -744.47f), + new(46.32f, -746.51f), new(45.47f, -748.65f), new(44.45f, -750.55f), new(43.2f, -752.35f), new(41.56f, -754.13f), new(39.5f, -755.65f), + new(39.26f, -755.76f), new(37.19f, -756.77f), new(34.98f, -757.44f), new(34.73f, -757.51f), new(32, -757.99f), new(29.25f, -757.99f)]); + private static readonly PolygonCustom shapeCustom6 = new([new(63.97f, -755.32f), new(60.49f, -758.06f), new(59.79f, -755.03f), + new(59.17f, -752.17f), new(58.78f, -750.63f), new(58.22f, -749.06f), new(57.51f, -747.51f), new(56.57f, -746.04f), new(55.33f, -744.71f), + new(54.02f, -743.57f), new(52.47f, -742.63f), new(50.81f, -741.78f), new(48.48f, -740.79f), new(47.65f, -740.45f), new(48.05f, -736.33f), + new(50.02f, -737.09f), new(52.5f, -738.17f), new(54.44f, -739.15f), new(56.42f, -740.38f), new(58.26f, -741.94f), new(59.73f, -743.55f), + new(61.01f, -745.52f), new(62, -747.65f), new(62.67f, -749.58f), new(63.14f, -751.48f), new(63.79f, -754.5f)]); + private static readonly List union = [shapeCustom1, shapeCustom2, shapeCustom3, shapeCustom4, shapeCustom5, shapeCustom6]; + private static readonly AOEShapeCustom risky = new(union); + private static readonly AOEShapeCustom notRisky = new(union, InvertForbiddenZone: true); + public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if ((SID)status.ID == SID.Heavy) - _inZone.Set(Raid.FindSlot(actor.InstanceID)); + if (active) + yield return new(ShouldBeInZone ? notRisky : risky, Module.Arena.Center, Color: ShouldBeInZone ? ArenaColor.SafeFromAOE : ArenaColor.AOE); } - public override void OnStatusLose(Actor actor, ActorStatus status) + public override void OnEventEnvControl(byte index, uint state) { - if ((SID)status.ID == SID.Heavy) - _inZone.Clear(Raid.FindSlot(actor.InstanceID)); + if (index == 0x00 && state == 0x00020001) + active = true; } public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -35,4 +64,14 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) if ((AID)spell.Action.ID == AID.InexorablePullAOE) ShouldBeInZone = false; } + + public override void AddHints(int slot, Actor actor, TextHints hints) + { + if (!ShouldBeInZone && ActiveAOEs(slot, actor).Any(c => c.Risky && c.Check(actor.Position))) + hints.Add(riskHint); + if (ShouldBeInZone && ActiveAOEs(slot, actor).Any(c => c.Risky && !c.Check(actor.Position))) + hints.Add(risk2Hint); + else if (ShouldBeInZone && ActiveAOEs(slot, actor).Any(c => c.Risky && c.Check(actor.Position))) + hints.Add(stayHint, false); + } } diff --git a/BossMod/Modules/Endwalker/Alliance/A23Halone/Octagons.cs b/BossMod/Modules/Endwalker/Alliance/A23Halone/Octagons.cs index 5e1b3b5753..af79977f41 100644 --- a/BossMod/Modules/Endwalker/Alliance/A23Halone/Octagons.cs +++ b/BossMod/Modules/Endwalker/Alliance/A23Halone/Octagons.cs @@ -7,8 +7,8 @@ namespace BossMod.Endwalker.Alliance.A23Halone; class Octagons(BossModule module) : Components.GenericAOEs(module) { private ArenaBounds? _arena; - private const float InnerRadius = 11.5f; - private const float OuterRadius = 12.5f; + private const float InnerRadius = 11.2f; // radii adjusted for hitbox radius + private const float OuterRadius = 13.5f; private const int Vertices = 8; private static readonly WPos[] spears = [new(-686, 592), new(-700, 616.2f), new(-714, 592)]; private static readonly Angle[] angle = [37.5f.Degrees(), 22.5f.Degrees(), -37.5f.Degrees()]; @@ -16,12 +16,12 @@ class Octagons(BossModule module) : Components.GenericAOEs(module) new Polygon(spears[0], OuterRadius, Vertices, angle[0]), new Polygon(spears[1], InnerRadius, Vertices, angle[1]), new Polygon(spears[1], OuterRadius, Vertices, angle[1]), new Polygon(spears[2], InnerRadius, Vertices, angle[2]), new Polygon(spears[2], OuterRadius, Vertices, angle[2])]; - private static readonly List baseArena = [new Circle(new WPos(-700, 600), 30)]; - public readonly List octagonsInner = []; - public readonly List octagonsOuter = []; - public static readonly ArenaBounds arenaDefault = new ArenaBoundsCircle(30); - public AOEInstance? _aoe; - public static readonly AOEShapeCustom customShape = new(baseArena, [shapes[0], shapes[2], shapes[4]]); + private static readonly List baseArena = [new Circle(new WPos(-700, 600), 29.5f)]; + private readonly List octagonsInner = []; + private readonly List octagonsOuter = []; + public static readonly ArenaBounds arenaDefault = new ArenaBoundsCircle(29.5f); + private AOEInstance? _aoe; + private static readonly AOEShapeCustom customShape = new(baseArena, [shapes[0], shapes[2], shapes[4]]); public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31Thaliak.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31Thaliak.cs index 6f0990f6f2..e4cef9fe37 100644 --- a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31Thaliak.cs +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31Thaliak.cs @@ -10,11 +10,4 @@ class Hydroptosis(BossModule module) : Components.SpreadFromCastTargets(module, class HieroglyphikaRightBank(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HieroglyphikaRightBank), new AOEShapeCone(60, 90.Degrees())); [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "Malediktus, LTS, veyn", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 962, NameID = 11298, SortOrder = 2)] -public class A31Thaliak(WorldState ws, Actor primary) : BossModule(ws, primary, NormalCenter, NormalBounds) -{ - public static readonly WPos NormalCenter = new(-945, 945); - public static readonly ArenaBoundsSquare NormalBounds = new(24); - public static readonly WPos TriangleCenter = new(-945, 941.5f); - public static readonly ArenaBoundsComplex TriangleBounds = new([new TriangleE(TriangleCenter, 24)]); - -} +public class A31Thaliak(WorldState ws, Actor primary) : BossModule(ws, primary, TetraktysBorder.NormalCenter, TetraktysBorder.NormalBounds); diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakStates.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakStates.cs index 939fcefbee..934a33b9f6 100644 --- a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakStates.cs +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakStates.cs @@ -24,7 +24,10 @@ private void SinglePhase(uint id) Rhyton(id + 0xC0000, 6.1f); RheognosisPetrine(id + 0xD0000, 7.6f); Thlipsis(id + 0xE0000, 2.1f); - + HieroglyphicaLeftRightBank(id + 0xF0000, 6.6f); + Hydroptosis(id + 0x100000, 2.1f); + Katarraktes(id + 0x110000, 5.2f); + Tetraktys2(id + 0x120000, 7.7f); SimpleState(id + 0xFF0000, 10, "???"); } @@ -42,6 +45,7 @@ private void Rheognosis(uint id, float delay) Cast(id, AID.Rheognosis, delay, 5) .ActivateOnEnter(); ComponentCondition(id + 0x10, 20.3f, comp => comp.NumCasts > 0, "Knockback") + .SetHint(StateMachine.StateHint.Knockback) .DeactivateOnExit() .SetHint(StateMachine.StateHint.Raidwide); } @@ -51,6 +55,7 @@ private void RheognosisPetrine(uint id, float delay) Cast(id, AID.RheognosisPetrine, delay, 5) .ActivateOnEnter(); ComponentCondition(id + 0x10, 20.3f, comp => comp.NumCasts > 0, "Knockback") + .SetHint(StateMachine.StateHint.Knockback) .ActivateOnEnter() .DeactivateOnExit() .SetHint(StateMachine.StateHint.Raidwide); @@ -98,9 +103,7 @@ private void Tetraktys1(uint id, float delay) CastStart(id, AID.Tetraktys, delay) .ActivateOnEnter(); // telegraph appears ~0.1s before cast start CastEnd(id + 1, 6); - ComponentCondition(id + 2, 0.4f, comp => comp.Active, "Triangles start") - .OnExit(() => Module.Arena.Bounds = A31Thaliak.TriangleBounds) - .OnExit(() => Module.Arena.Center = A31Thaliak.TriangleCenter); + ComponentCondition(id + 2, 0.4f, comp => comp.Active, "Triangles start"); ComponentCondition(id + 0x10, 3.6f, comp => comp.AOEs.Count > 0) .ActivateOnEnter(); ComponentCondition(id + 0x11, 3.9f, comp => comp.NumCasts >= 3, "Small tri 1"); @@ -123,9 +126,7 @@ private void Tetraktys1(uint id, float delay) .DeactivateOnExit() .DeactivateOnExit(); ComponentCondition(id + 0x200, 4.2f, comp => !comp.Active, "Triangles resolve") - .DeactivateOnExit() - .OnExit(() => Module.Arena.Bounds = A31Thaliak.NormalBounds) - .OnExit(() => Module.Arena.Center = A31Thaliak.NormalCenter); + .DeactivateOnExit(); } private void Tetraktys2(uint id, float delay) @@ -133,9 +134,7 @@ private void Tetraktys2(uint id, float delay) CastStart(id, AID.Tetraktys, delay) .ActivateOnEnter(); // telegraph appears ~0.1s before cast start CastEnd(id + 1, 6); - ComponentCondition(id + 2, 0.4f, comp => comp.Active, "Triangles start") - .OnExit(() => Module.Arena.Bounds = A31Thaliak.TriangleBounds) - .OnExit(() => Module.Arena.Center = A31Thaliak.TriangleCenter); + ComponentCondition(id + 2, 0.4f, comp => comp.Active, "Triangles start"); ComponentCondition(id + 0x10, 3.6f, comp => comp.AOEs.Count > 0) .ActivateOnEnter(); ComponentCondition(id + 0x11, 3.9f, comp => comp.NumCasts >= 3, "Small tri 1"); @@ -158,9 +157,7 @@ private void Tetraktys2(uint id, float delay) .DeactivateOnExit(); ComponentCondition(id + 0x200, 3.2f, comp => !comp.Active, "Triangles resolve") - .DeactivateOnExit() - .OnExit(() => Module.Arena.Bounds = A31Thaliak.NormalBounds) - .OnExit(() => Module.Arena.Center = A31Thaliak.NormalCenter); + .DeactivateOnExit(); } private void Hieroglyphica(uint id, float delay) diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/Tetraktys.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/Tetraktys.cs index 595e9bf3f9..18c585aa6a 100644 --- a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/Tetraktys.cs +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/Tetraktys.cs @@ -2,12 +2,17 @@ class TetraktysBorder(BossModule module) : Components.GenericAOEs(module) { + public static readonly WPos NormalCenter = new(-945, 945); + public static readonly ArenaBoundsSquare NormalBounds = new(24); + private static readonly WPos TriangleCenter = new(-945, 941.5f); + private static readonly TriangleE triangle = new(TriangleCenter, 48); + private static readonly Square square = new(NormalCenter, 24); + private static readonly ArenaBoundsComplex TriangleBounds = new([triangle]); + private static readonly AOEShapeCustom transition = new([square], [triangle]); + private AOEInstance? _aoe; public bool Active; - private readonly List _aoes = []; - private static readonly AOEShapeRect _shape = new(50, 24); - - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnEventEnvControl(byte index, uint state) { @@ -15,19 +20,18 @@ public override void OnEventEnvControl(byte index, uint state) { switch (state) { - case 0x00200010: // telegraph - emulate by three rects from center of each side - var apex = Module.Center - new WDir(0, Module.Bounds.Radius); - var height = Module.Bounds.Radius * Helpers.sqrt3; - var activation = WorldState.FutureTime(6.5f); - _aoes.Add(new(_shape, apex + new WDir(0, height), default, activation)); - _aoes.Add(new(_shape, apex + 0.5f * new WDir(+Module.Bounds.Radius, height), 120.Degrees(), activation)); - _aoes.Add(new(_shape, apex + 0.5f * new WDir(-Module.Bounds.Radius, height), -120.Degrees(), activation)); + case 0x00200010: + _aoe = new(transition, NormalCenter, default, WorldState.FutureTime(6.5f)); break; case 0x00020001: - _aoes.Clear(); + _aoe = null; + Module.Arena.Bounds = TriangleBounds; + Module.Arena.Center = TriangleCenter; Active = true; break; case 0x00080004: + Module.Arena.Bounds = NormalBounds; + Module.Arena.Center = NormalCenter; Active = false; break; } diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenStates.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenStates.cs index be3aea4e70..c788b0b155 100644 --- a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenStates.cs +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenStates.cs @@ -30,7 +30,10 @@ private void SinglePhase(uint id) WindRoseSeafoamSpiral(id + 0x120000, 11.3f); StormySeas(id + 0x130000, 2.9f); LeftRightStrait(id + 0x140000, 0.3f); - + Tempest(id + 0x150000, 6.2f); + NavigatorsTridentAdds(id + 0x160000, 11.5f); + WindRoseSeafoamSpiral(id + 0x170000, 5); + SurgingWaveToTheLast(id + 0x180000, 2.1f); SimpleState(id + 0xFF0000, 10, "???"); } diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33Oschon.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33Oschon.cs index 858971233d..a76622b1d3 100644 --- a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33Oschon.cs +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33Oschon.cs @@ -10,6 +10,8 @@ class P1Downhill(BossModule module) : Components.LocationTargetedAOEs(module, Ac class P2MovingMountains(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.MovingMountains)); class P2PeakPeril(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.PeakPeril)); class P2Shockwave(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.Shockwave)); +class P2SuddenDownpour(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.P2SuddenDownpourAOE)); + class P2PitonPull(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.PitonPullAOE), 22); class P2Altitude(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.AltitudeAOE), 6); class P2Arrow(BossModule module) : Components.BaitAwayCast(module, ActionID.MakeSpell(AID.ArrowP2AOE), new AOEShapeCircle(10), true); diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonEnum.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonEnum.cs index cc2351dd4e..dfe229f697 100644 --- a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonEnum.cs +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonEnum.cs @@ -75,6 +75,10 @@ public enum AID : uint WanderingVolleyDownhillAOE = 35234, // Helper->location, 14.0s cast, range 8 circle WanderingVolleyN = 35244, // BossP2->self, 10.0s cast, range 40 width 40 rect, knockback 12 left/right + huge circle N WanderingVolleyS = 35245, // BossP2->self, 10.0s cast, range 40 width 40 rect, knockback 12 left/right + huge circle S + + P2SuddenDownpour = 35226, // BossP2->self, 4.0+1,0s cast, single-target + P2SuddenDownpourAOE = 36027, // Helper->self, 5.0s cast, range 60 circle + } public enum IconID : uint diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonStates.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonStates.cs index 1f28860539..8fec5c591d 100644 --- a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonStates.cs +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonStates.cs @@ -29,7 +29,12 @@ private void Phase1(uint id) P1TrekShot(id + 0xB0000, 1.3f); P1SuddenDownpour(id + 0xC0000, 2.6f); P1SuddenDownpour(id + 0xD0000, 1.1f); - + P1Reproduce(id + 0xE0000, 7.4f); + P1SuddenDownpour(id + 0xF0000, 0.1f); + P1DownhillClimbingShot(id + 0x100000, 4.4f); + P1FlintedFoehn(id + 0x110000, 3.1f); + P1TrekShot(id + 0x120000, 1.4f); + P1SuddenDownpour(id + 0x130000, 2.6f); SimpleState(id + 0xFF0000, 10, "???"); } @@ -48,7 +53,9 @@ private void Phase2(uint id) P2FlintedFoehn(id + 0xA0000, 5.7f); P2Arrow(id + 0xB0000, 1.1f); P2Altitude(id + 0xC0000, 2.2f); - + P2SuddenDownpour(id + 0xD0000, 7.2f); + P2SuddenDownpour(id + 0xE0000, 5.1f); + P2ArrowTrail(id + 0xF0000, 7.2f); SimpleState(id + 0xFF0000, 10, "???"); } @@ -112,6 +119,7 @@ private void P1DownhillClimbingShot(uint id, float delay) ComponentCondition(id + 0x10, 0.4f, comp => comp.Casters.Count > 0) .ActivateOnEnter(); ActorCastMulti(id + 0x20, _module.BossP1, [AID.ClimbingShot1, AID.ClimbingShot2, AID.ClimbingShot3, AID.ClimbingShot4], 1.7f, 5, true, "Knockback") + .SetHint(StateMachine.StateHint.Knockback) .ActivateOnEnter() .DeactivateOnExit(); ComponentCondition(id + 0x30, 1.8f, comp => comp.NumCasts > 0, "Puddles") @@ -235,4 +243,13 @@ private void P2WanderingVolley(uint id, float delay) ComponentCondition(id + 0x43, 0.5f, comp => comp.NumCasts > 0, "Diagonal circles") .DeactivateOnExit(); } + + private void P2SuddenDownpour(uint id, float delay) + { + ActorCast(id, _module.BossP2, AID.P2SuddenDownpour, delay, 4, true); + ComponentCondition(id + 2, 1, comp => comp.NumCasts > 0, "Raidwide") + .ActivateOnEnter() + .DeactivateOnExit() + .SetHint(StateMachine.StateHint.Raidwide); + } } diff --git a/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34Eulogia.cs b/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34Eulogia.cs index ae8ffbc127..e1f1c09863 100644 --- a/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34Eulogia.cs +++ b/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34Eulogia.cs @@ -1,5 +1,53 @@ namespace BossMod.Endwalker.Alliance.A34Eulogia; +class ArenaChanges(BossModule module) : Components.GenericAOEs(module) +{ + public static readonly WPos Center = new(945, -945); + private static readonly ArenaBoundsSquare squareBounds = new(24); + private static readonly ArenaBoundsCircle smallerBounds = new(30); + public static readonly ArenaBoundsCircle BigBounds = new(35); + private static readonly Circle circle = new(Center, 30); + private static readonly Square square = new(Center, 24); + private static readonly AOEShapeCustom transitionSquare = new([circle], [square]); + private static readonly AOEShapeDonut transitionSmallerBounds = new(30, 35); + private AOEInstance? _aoe; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + + public override void OnEventEnvControl(byte index, uint state) + { + if (index == 0x1B) + { + if (state == 0x00080004) + Module.Arena.Bounds = BigBounds; + if (state == 0x00100001) + Module.Arena.Bounds = smallerBounds; + } + } + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Hieroglyphika) + _aoe = new(transitionSquare, Center, default, spell.NPCFinishAt); + if ((AID)spell.Action.ID == AID.Whorl) + _aoe = new(transitionSmallerBounds, Center, default, spell.NPCFinishAt); + } + + public override void OnCastFinished(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Hieroglyphika) + { + Module.Arena.Bounds = squareBounds; + _aoe = null; + } + if ((AID)spell.Action.ID == AID.Whorl) + { + Module.Arena.Bounds = smallerBounds; + _aoe = null; + } + } +} + class Sunbeam(BossModule module) : Components.BaitAwayCast(module, ActionID.MakeSpell(AID.SunbeamAOE), new AOEShapeCircle(6), true); class DestructiveBolt(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.DestructiveBoltAOE), 6, 8); class HandOfTheDestroyerWrath(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HandOfTheDestroyerWrathAOE), new AOEShapeRect(90, 20)); @@ -8,9 +56,4 @@ class DestructiveBolt(BossModule module) : Components.StackWithCastTargets(modul class EudaimonEorzea(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.EudaimonEorzeaAOE)); [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "Malediktus, LTS, veyn", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 962, NameID = 11301, SortOrder = 7)] -public class A34Eulogia(WorldState ws, Actor primary) : BossModule(ws, primary, new(945, -945), DefaultBounds) -{ - public static readonly ArenaBoundsCircle DefaultBounds = new(35); - public static readonly ArenaBoundsCircle SmallerBounds = new(30); - public static readonly ArenaBoundsSquare HieroglyphikaBounds = new(24); -} +public class A34Eulogia(WorldState ws, Actor primary) : BossModule(ws, primary, ArenaChanges.Center, ArenaChanges.BigBounds); diff --git a/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34EulogiaStates.cs b/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34EulogiaStates.cs index 2738f13265..5e44d40eb9 100644 --- a/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34EulogiaStates.cs +++ b/BossMod/Modules/Endwalker/Alliance/A34Eulogia/A34EulogiaStates.cs @@ -4,7 +4,8 @@ class A34EulogiaStates : StateMachineBuilder { public A34EulogiaStates(BossModule module) : base(module) { - DeathPhase(0, SinglePhase); + DeathPhase(0, SinglePhase) + .ActivateOnEnter(); } public void SinglePhase(uint id) @@ -31,6 +32,17 @@ public void SinglePhase(uint id) Quintessence(id + 0x110000, 17.7f); Sunbeam(id + 0x120000, 7.2f); Whorl(id + 0x130000, 10.7f); + + MechanicsInRandomOrder(id + 0x140000, 241.7f); + DawnOfTime(id + 0x150000, 0); + Quintessence(id + 0x160000, 13.6f); // no logs past this here, just guessed + Sunbeam(id + 0x170000, 7.2f); + Whorl(id + 0x180000, 8.6f); + MechanicsInRandomOrder(id + 0x190000, 241.7f); + } + + private void MechanicsInRandomOrder(uint id, float delay) + { // following mechanics order is either fully random, or has multiple possible forks... //Hydrostasis(id + 0x140000, 3.2f); //SolarFans(id + 0x150000, 2.5f); @@ -38,25 +50,25 @@ public void SinglePhase(uint id) //ThousandfoldThrust(id + 0x170000, 1.2f); //DestructiveBolt(id + 0x180000, 2.5f); //LovesLight(id + 0x190000, 5.4f); - SimpleState(id + 0xFF0000, 100, "Mechanics in random order") - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter(); + Timeout(id + 0x140000, 241.7f, "Mechanics in random order") + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); } private void DawnOfTime(uint id, float delay) @@ -90,7 +102,6 @@ private void Sunbeam(uint id, float delay) private void Whorl(uint id, float delay) { Cast(id, AID.Whorl, delay, 7, "Raidwide") - .OnExit(() => Module.Arena.Bounds = A34Eulogia.SmallerBounds) .SetHint(StateMachine.StateHint.Raidwide); } @@ -129,7 +140,8 @@ private void Hydrostasis(uint id, float delay) Cast(id, AID.Hydrostasis, delay, 4); Cast(id + 0x10, AID.TimeAndTide, 2.1f, 6) .ActivateOnEnter(); - ComponentCondition(id + 0x20, 2.9f, comp => comp.NumCasts > 0, "Knockback 1"); + ComponentCondition(id + 0x20, 2.9f, comp => comp.NumCasts > 0, "Knockback 1") + .SetHint(StateMachine.StateHint.Knockback); ComponentCondition(id + 0x21, 3, comp => comp.NumCasts > 1, "Knockback 2"); ComponentCondition(id + 0x22, 3, comp => comp.NumCasts > 2, "Knockback 3") .DeactivateOnExit(); @@ -145,8 +157,7 @@ private void DestructiveBolt(uint id, float delay) private void HieroglyphikaHandOfTheDestroyer(uint id, float delay) { - Cast(id, AID.Hieroglyphika, delay, 5) - .OnEnter(() => Module.Arena.Bounds = A34Eulogia.HieroglyphikaBounds); + Cast(id, AID.Hieroglyphika, delay, 5); ComponentCondition(id + 0x10, 1, comp => comp.AOEs.Count > 0) .ActivateOnEnter(); ComponentCondition(id + 0x20, 12.8f, comp => comp.BindsAssigned, "Binds"); @@ -154,8 +165,7 @@ private void HieroglyphikaHandOfTheDestroyer(uint id, float delay) ComponentCondition(id + 0x31, 2.8f, comp => comp.NumCasts > 0, "Squares") .ActivateOnEnter() .ActivateOnEnter() - .DeactivateOnExit() - .OnExit(() => Module.Arena.Bounds = A34Eulogia.SmallerBounds); + .DeactivateOnExit(); CastEnd(id + 0x32, 4.7f); Condition(id + 0x33, 0.5f, () => Module.FindComponent()?.NumCasts > 0 || Module.FindComponent()?.NumCasts > 0, "Half-arena cleave") .DeactivateOnExit() @@ -212,6 +222,7 @@ private void AsAboveSoBelow(uint id, float delay) .ActivateOnEnter() .ActivateOnEnter(); ComponentCondition(id + 0x20, 0.2f, comp => comp.NumCasts > 0, "Knockback") + .SetHint(StateMachine.StateHint.Knockback) .DeactivateOnExit(); ComponentCondition(id + 0x30, 0.8f, comp => comp.NumCasts > 0, "Exaflare start"); Cast(id + 0x40, AID.SoaringMinuet, 2.3f, 7, "Wide cleave") @@ -226,7 +237,6 @@ private void EudaimonEorzea(uint id, float delay) ComponentCondition(id + 0x10, 2.7f, comp => comp.NumCasts > 0, "Raidwide x13") .ActivateOnEnter() .DeactivateOnExit() - .OnExit(() => Module.Arena.Bounds = A34Eulogia.DefaultBounds) .SetHint(StateMachine.StateHint.Raidwide); } } diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4.cs index 70359c1798..ac9d1f7760 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4.cs @@ -13,7 +13,7 @@ class IcyPortent(BossModule module) : Components.CastHint(module, ActionID.MakeS class PawnOff(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.PawnOffReal), new AOEShapeCircle(20)); class Fracture(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.Fracture)); // TODO: consider showing reflect hints -[ModuleInfo(BossModuleInfo.Maturity.Verified, PrimaryActorOID = (uint)OID.Knight, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9838)] +[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "veyn, Malediktus", PrimaryActorOID = (uint)OID.Knight, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9838)] public class DRS4 : BossModule { private readonly IReadOnlyList _warrior; diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4Enums.cs index 3af2aa320d..59eaeb755c 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4Enums.cs @@ -90,6 +90,12 @@ public enum AID : uint PawnOffReal = 22585, // SoldierAvatar->self, 7.0s cast, range 20 circle aoe PawnOffFake = 22586, // SoldierAvatar->self, 7.0s cast, range 20 circle fake aoe + AutomaticTurret2 = 22597, // Gunner->self, 3.0s cast, single-target + TurretsTour = 22598, // Gunner->self, 5.0s cast, single-target + TurretsTourAOE1 = 22599, // Helper->location, 5.0s cast, width 6 rect charge + TurretsTourAOE2 = 22601, // AutomaticTurret->self, no cast, range 55 width 6 rect + TurretsTourAOE3 = 22600, // AutomaticTurret->location, no cast, width 6 rect charge + SpitefulSpirit = 22574, // Warrior->self, 5.0s cast, single-target, visual (summon spheres?) StrongpointDefense = 22558, // Knight->self, 5.0s cast, single-target, visual (summon wards?) CoatOfArmsFB = 22559, // AetherialWard->self, 4.0s cast, single-target, applies front/back directional parry diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4States.cs index 257b6924f5..3aad1e9ae7 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/DRS4States.cs @@ -50,8 +50,11 @@ private void Phase2(uint id) P2GunTurret(id + 0x40000, 10.2f); P2DoubleGambit(id + 0x50000, 10.3f); P2BloodAndBone(id + 0x60000, 5.7f); - // TODO: tankbusters -> 4th battery -> raidwides -> tankbusters -> enrage - SimpleState(id + 0xFF0000, 100, "???"); + P2RapidSever(id + 0x70000, 4.1f); + P2TurretsTour(id + 0x80000, 10.2f); + P2BloodAndBone(id + 0x90000, 5.1f); + P2RapidSever(id + 0xA0000, 4.1f); + SimpleState(id + 0xB0000, 15, "Enrage"); // TODO: not sure about the timing, no log yet } private void Phase3(uint id) @@ -188,6 +191,21 @@ private void P2RapidSever(uint id, float delay) .SetHint(StateMachine.StateHint.Tankbuster); } + private void P2TurretsTour(uint id, float delay) + { + ActorCast(id, _module.Gunner, AID.RelentlessBatteryGunner, delay, 5); // both soldier and gunner cast their visual + ActorCast(id + 0x10, Module.Enemies(OID.Gunner).FirstOrDefault, AID.AutomaticTurret2, 3, 3, false, "Turrets spawn"); + ActorCast(id + 0x20, Module.Enemies(OID.Gunner).FirstOrDefault, AID.TurretsTour, 3.1f, 5, false, "Turrets start") + .ActivateOnEnter(); + ComponentCondition(id + 0x30, 1.6f, comp => comp.NumCasts >= 4, "Turrets resolve") + .DeactivateOnExit(); + ActorCastMulti(id + 0x40, _module.Soldier, new[] { AID.FieryPortent, AID.IcyPortent }, 0.2f, 5.2f, false, "Move/stay") + .ActivateOnEnter() + .ActivateOnEnter() + .DeactivateOnExit() + .DeactivateOnExit(); + } + private void P2FoolsGambit(uint id, float delay) { ActorCast(id, _module.Soldier, AID.RelentlessBatterySoldier, delay, 5); // both soldier and gunner cast their visual diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/TurretsTour.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/TurretsTour.cs new file mode 100644 index 0000000000..2330c06822 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS4QueensGuard/TurretsTour.cs @@ -0,0 +1,57 @@ +namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard; + +class TurretsTour(BossModule module) : Components.GenericAOEs(module) +{ + private readonly List<(Actor turret, AOEShapeRect shape)> _turrets = []; + private readonly List<(Actor caster, AOEShapeRect shape, Angle rotation)> _casters = []; + private DateTime _activation; + + private static readonly AOEShapeRect _defaultShape = new(55, 3); + + public override void Update() + { + if (_turrets.Count == 0) + { + var turrets = Module.Enemies(OID.AutomaticTurret); + foreach (var t in turrets) + { + var target = turrets.Exclude(t).InShape(_defaultShape, t).Closest(t.Position); + var shape = target != null ? _defaultShape with { LengthFront = (target.Position - t.Position).Length() } : _defaultShape; + _turrets.Add((t, shape)); + } + } + } + + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + foreach (var t in _turrets) + yield return new(t.shape, t.turret.Position, t.turret.Rotation, _activation); + foreach (var c in _casters) + yield return new(c.shape, c.caster.Position, c.rotation, c.caster.CastInfo?.NPCFinishAt ?? default); + } + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.TurretsTourAOE1) + { + var toTarget = spell.LocXZ - caster.Position; + _casters.Add((caster, new AOEShapeRect(toTarget.Length(), _defaultShape.HalfWidth), Angle.FromDirection(toTarget))); + _activation = spell.NPCFinishAt; + } + } + + public override void OnCastFinished(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.TurretsTourAOE1) + _casters.RemoveAll(c => c.caster == caster); + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.TurretsTourAOE2 or AID.TurretsTourAOE3) + { + _turrets.RemoveAll(t => t.turret == caster); + ++NumCasts; + } + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7.cs index 6a40b81431..cec5fddb80 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7.cs @@ -9,7 +9,7 @@ class ThunderousDischarge(BossModule module) : Components.CastCounter(module, Ac class Electrocution(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Electrocution), 3); // TODO: ManaFlame component - show reflect hints -[ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9759)] +[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "veyn, Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9759)] public class DRS7 : BossModule { private readonly IReadOnlyList _monks; diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7Enums.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7Enums.cs index 705bdad5b2..620f430dc1 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7Enums.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7Enums.cs @@ -43,6 +43,10 @@ public enum AID : uint DevastatingBoltInner = 22470, // Helper->self, 4.0s cast, range 12-17 donut RendingBolt = 22475, // Boss->self, 3.0s cast, single-target, visual (electrocution puddles) Electrocution = 22476, // Helper->location, 3.0s cast, range 3 circle puddles + ManaBurnFirst = 22495, // BallOfFire->self, 8.0s cast, range 80 circle + ManaBurnRepeat = 20506, // Helper->self, no cast, range 80 circle + SandBurstFirst = 22498, // BallOfEarth->self, 8.0s cast, range 80 circle + SandBurstRepeat = 20507, // Helper->self, no cast, range 80 circle } public enum SID : uint diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7States.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7States.cs index 1761797d0b..a49c9f3714 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7States.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS7StygimolochLord/DRS7States.cs @@ -33,7 +33,10 @@ void PhaseAdds(uint id) RendingBolt(id + 0x30000, 2.8f); LabyrinthineFateDevastatingBoltRendingBoltFatefulWords(id + 0x40000, 7.3f); RendingBoltDevastatingBolt(id + 0x50000, 4.6f); - // TODO: repeat? + LabyrinthineFateDevastatingBoltRendingBoltFatefulWords(id + 0x60000, 7.3f); + RendingBoltDevastatingBolt(id + 0x70000, 4.6f); + LabyrinthineFateDevastatingBoltRendingBoltFatefulWords(id + 0x80000, 7.3f); + RendingBoltDevastatingBolt(id + 0x90000, 4.6f); SimpleState(id + 0xFF0000, 100, "???"); } diff --git a/BossMod/Replay/Visualization/OpList.cs b/BossMod/Replay/Visualization/OpList.cs index 8256e3874a..1e717627f5 100644 --- a/BossMod/Replay/Visualization/OpList.cs +++ b/BossMod/Replay/Visualization/OpList.cs @@ -115,6 +115,8 @@ private bool FilterOp(WorldState.Operation o) ActorState.OpEventObjectStateChange op => FilterInterestingActor(op.InstanceID, op.Timestamp, false), ActorState.OpEventObjectAnimation op => FilterInterestingActor(op.InstanceID, op.Timestamp, false), ActorState.OpRename op => FilterInterestingActor(op.InstanceID, op.Timestamp, false), + ActorState.OpIcon op => FilterInterestingActor(op.InstanceID, op.Timestamp, false), + ActorState.OpTether op => FilterInterestingActor(op.InstanceID, op.Timestamp, false), ClientState.OpActionRequest => false, //ClientState.OpActionReject => false, ClientState.OpCooldown => false, diff --git a/BossMod/Util/ShapeDistance.cs b/BossMod/Util/ShapeDistance.cs index 4a1eec159e..0cfb0e0bc5 100644 --- a/BossMod/Util/ShapeDistance.cs +++ b/BossMod/Util/ShapeDistance.cs @@ -206,15 +206,60 @@ public static Func InvertedConvexPolygon(IEnumerable vertices return p => -convexpolygon(p); } - // TODO: This is not very efficient for complex polygons, need to find a better way public static Func ConcavePolygon(IEnumerable vertices) { + var edges = PolygonUtil.EnumerateEdges(vertices).ToList(); return p => { - return p.InConcavePolygon(vertices) ? 0 : float.MaxValue; + var isInside = IsPointInsideConcavePolygon(p, vertices); + var minDistance = edges.Min(e => DistanceToEdge(p, e)); + return isInside ? -minDistance : minDistance; }; } + private static bool IsPointInsideConcavePolygon(WPos point, IEnumerable vertices) + { + var intersections = 0; + var verticesList = vertices.ToList(); + for (var i = 0; i < verticesList.Count; i++) + { + var a = verticesList[i]; + var b = verticesList[(i + 1) % verticesList.Count]; + if (RayIntersectsEdge(point, a, b)) + { + intersections++; + } + } + return intersections % 2 != 0; + } + + private static bool RayIntersectsEdge(WPos point, WPos a, WPos b) + { + if (a.Z > b.Z) + (b, a) = (a, b); + if (point.Z == a.Z || point.Z == b.Z) + point = new WPos(point.X, point.Z + 0.0001f); + if (point.Z > b.Z || point.Z < a.Z || point.X >= Math.Max(a.X, b.X)) + return false; + if (point.X < Math.Min(a.X, b.X)) + return true; + var red = (point.Z - a.Z) / (b.Z - a.Z); + var blue = (b.X - a.X) * red + a.X; + return point.X < blue; + } + + private static float DistanceToEdge(WPos point, (WPos p1, WPos p2) edge) + { + var (p1, p2) = edge; + var edgeVector = p2 - p1; + var pointVector = point - p1; + var edgeLengthSquared = edgeVector.LengthSq(); + + var t = Math.Max(0, Math.Min(1, pointVector.Dot(edgeVector) / edgeLengthSquared)); + var projection = p1 + t * edgeVector; + return (point - projection).Length(); + } + private static Func Intersection(List> funcs, float offset = 0) => p => funcs.Max(e => e(p)) - offset; private static Func Union(List> funcs, float offset = 0) => p => funcs.Min(e => e(p)) - offset; }