diff --git a/BossMod/BossModule/AOEShapes.cs b/BossMod/BossModule/AOEShapes.cs index 7d02c3ce8c..12058f3f97 100644 --- a/BossMod/BossModule/AOEShapes.cs +++ b/BossMod/BossModule/AOEShapes.cs @@ -206,8 +206,7 @@ public override bool Check(WPos position, WPos origin, Angle rotation) public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) { var (p1, p2, p3) = Helpers.CalculateEquilateralTriangleVertices(origin, rotation + DirectionOffset, SideLength); - var clippedVertices = arena.Bounds.ClipAndTriangulate([p1, p2, p3]); - arena.Zone(clippedVertices, color); + arena.AddTriangleFilled(p1, p2, p3, color); } public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.Danger) { diff --git a/BossMod/BossModule/ArenaBounds.cs b/BossMod/BossModule/ArenaBounds.cs index 2e2197c089..b928719870 100644 --- a/BossMod/BossModule/ArenaBounds.cs +++ b/BossMod/BossModule/ArenaBounds.cs @@ -9,7 +9,7 @@ public abstract class ArenaBounds(WPos center, float halfSize) public float HalfSize { get; init; } = halfSize; // largest horizontal/vertical dimension: radius for circle, max of width/height for rect // fields below are used for clipping - public float MaxApproxError { get; private set; } + public float MaxApproxError { get; private set; } = 0.05f; private readonly Clip2D _clipper = new(); public IEnumerable ClipPoly => _clipper.ClipPoly; @@ -36,6 +36,8 @@ public float ScreenHalfSize public abstract Pathfinding.Map BuildMap(float resolution = 0.5f); public abstract bool Contains(WPos p); public abstract float IntersectRay(WPos origin, WDir dir); + public override int GetHashCode() => HashCode.Combine(Center, HalfSize, ClipPoly); + public abstract WDir ClampToBounds(WDir offset, float scale = 1); public WPos ClampToBounds(WPos position) => Center + ClampToBounds(position - Center); @@ -107,7 +109,7 @@ public float ScreenHalfSize return ClipAndTriangulate([start + side, start - side, end - side, end + side]); } - public static (float, float, WPos) CalculateHalfSizeAndCenter(IEnumerable points) + public static (WPos center, float HalfHeight, float Halfwidth) CalculatePolygonProperties(IEnumerable points) { float minX = float.MaxValue; float maxX = float.MinValue; @@ -126,7 +128,7 @@ public static (float, float, WPos) CalculateHalfSizeAndCenter(IEnumerable float halfHeight = (maxZ - minZ) / 2; WPos center = new((minX + maxX) / 2, (minZ + maxZ) / 2); - return (halfWidth, halfHeight, center); + return (center, halfHeight, halfWidth); } } @@ -164,7 +166,7 @@ public override IEnumerable BuildClipPoly(float offset) yield return Center + new WDir(-s, -s); } - public override Pathfinding.Map BuildMap(float resolution) => new Pathfinding.Map(resolution, Center, HalfSize, HalfSize); + public override Pathfinding.Map BuildMap(float resolution) => new(resolution, Center, HalfSize, HalfSize); public override bool Contains(WPos position) => WPos.AlmostEqual(position, Center, HalfSize); public override float IntersectRay(WPos origin, WDir dir) => Intersect.RayRect(origin, dir, Center, new(0, 1), HalfSize, HalfSize); @@ -196,7 +198,7 @@ public override IEnumerable BuildClipPoly(float offset) yield return Center - dx - dz; } - public override Pathfinding.Map BuildMap(float resolution) => new Pathfinding.Map(resolution, Center, HalfWidth, HalfHeight, Rotation); + public override Pathfinding.Map BuildMap(float resolution) => new(resolution, Center, HalfWidth, HalfHeight, Rotation); public override bool Contains(WPos position) => position.InRect(Center, Rotation, HalfHeight, HalfHeight, HalfWidth); public override float IntersectRay(WPos origin, WDir dir) => Intersect.RayRect(origin, dir, Center, Rotation.ToDirection(), HalfWidth, HalfHeight); @@ -213,17 +215,17 @@ public override WDir ClampToBounds(WDir offset, float scale) } } -//should work for any non self-intersecting polygon with a list of points +//should work for any simple (no self intersections or holes) polygon with an IEnumerable/List of points public class ArenaBoundsPolygon : ArenaBounds { public float HalfWidth { get; private set; } public float HalfHeight { get; private set; } public readonly IEnumerable Points; - public ArenaBoundsPolygon(IEnumerable points) : base(CalculateHalfSizeAndCenter(points).Item3, MathF.Max(CalculateHalfSizeAndCenter(points).Item1, CalculateHalfSizeAndCenter(points).Item2)) + public ArenaBoundsPolygon(IEnumerable points) : base(CalculatePolygonProperties(points).center, MathF.Max(CalculatePolygonProperties(points).Halfwidth, CalculatePolygonProperties(points).HalfHeight)) { Points = points; - (HalfWidth, HalfHeight, Center) = CalculateHalfSizeAndCenter(points); + (Center, HalfHeight, HalfWidth) = CalculatePolygonProperties(points); } public override IEnumerable BuildClipPoly(float offset) => Points; @@ -232,7 +234,7 @@ public ArenaBoundsPolygon(IEnumerable points) : base(CalculateHalfSizeAndC public override Pathfinding.Map BuildMap(float resolution) { - var map = new Pathfinding.Map(resolution, CalculateHalfSizeAndCenter(Points).Item3, CalculateHalfSizeAndCenter(Points).Item1, CalculateHalfSizeAndCenter(Points).Item2); + var map = new Pathfinding.Map(resolution, CalculatePolygonProperties(Points).center, CalculatePolygonProperties(Points).Halfwidth, CalculatePolygonProperties(Points).HalfHeight); map.BlockPixelsInside(ShapeDistance.InvertedPolygon(Points), 0, 0); return map; } @@ -279,3 +281,285 @@ public override WDir ClampToBounds(WDir offset, float scale) return closestPoint - Center; } } + +public class ArenaBoundsDonut(WPos center, float innerRadius, float outerRadius) : ArenaBounds(center, outerRadius) +{ + public float OuterRadius { get; init; } = outerRadius; + public float InnerRadius { get; init; } = innerRadius; + + public override IEnumerable BuildClipPoly(float offset = 0) => CurveApprox.Donut(Center, InnerRadius - offset, OuterRadius + offset, MaxApproxError); + + public override Pathfinding.Map BuildMap(float resolution = 0.5f) + { + var map = new Pathfinding.Map(resolution, Center, OuterRadius, OuterRadius); + map.BlockPixelsInside(ShapeDistance.InvertedDonut(Center, InnerRadius, OuterRadius), 0, 0); + return map; + } + + public override bool Contains(WPos position) + { + float distanceFromCenter = (position - Center).Length(); + return distanceFromCenter < OuterRadius && distanceFromCenter > InnerRadius; + } + + public override float IntersectRay(WPos origin, WDir dir) + { + float outerIntersection = Intersect.RayCircle(origin, dir, Center, OuterRadius); + float innerIntersection = Intersect.RayCircle(origin, dir, Center, InnerRadius); + float minOuter = outerIntersection >= 0 ? outerIntersection : float.MaxValue; + float minInner = innerIntersection >= 0 ? innerIntersection : float.MaxValue; + return Math.Min(minOuter, minInner); + } + + public override WDir ClampToBounds(WDir offset, float scale = 1) + { + var normOffset = offset.Normalized(); + var scaledOuter = OuterRadius * scale; + var scaledInner = InnerRadius * scale; + var distance = offset.Length(); + + if (distance > scaledOuter) + return normOffset * scaledOuter; + else if (distance < scaledInner) + return normOffset * scaledInner; + return offset; + } +} + +//for unions of different ArenaBounds, shapes that have clockwise winding order get united, counterclockwise differentiated +//buggy if there are more than 2 disjointed shapes or more than one non-self-intersecting hole, consider improving BuildClipPoly if such an arena shape is needed +public class ArenaBoundsUnion : ArenaBounds +{ + private const float ScalingFactor = 1000000; + private List _boundsList; + public List BoundsList + { + get => _boundsList; + set + { + if (!ReferenceEquals(_boundsList, value)) //clear caches for clamptobounds, intersectray and contains if union composition gets modified, BuildClipPolygon and BuildMap stays incase map gets reverted later + { + _boundsList = value; + _containsCache.Clear(); + _intersectRayCache.Clear(); + _clampToBoundsCache.Clear(); + _cachedUnionProperties = null; + } + } + } + + public ArenaBoundsUnion(IEnumerable bounds) : base(default, default) + { + _boundsList = bounds.ToList(); + _cachedUnionProperties = CalculateUnionProperties2(_boundsList); + + var props = _cachedUnionProperties.Value; + Center = props.Center; + HalfSize = MathF.Max(props.HalfHeight, props.HalfWidth); + } + + private (WPos Center, float HalfHeight, float HalfWidth)? _cachedUnionProperties; + private readonly Dictionary, IEnumerable> _polyCache = []; + private readonly Dictionary _containsCache = []; + private readonly Dictionary<(WPos, WDir), float> _intersectRayCache = []; + private readonly Dictionary, Pathfinding.Map> _buildMapCache = []; + private readonly Dictionary<(WPos, WDir), WDir> _clampToBoundsCache = []; + + private (WPos Center, float HalfHeight, float HalfWidth) CalculateUnionProperties() + { + if (!_cachedUnionProperties.HasValue) + _cachedUnionProperties = CalculateUnionProperties2(_boundsList); + return _cachedUnionProperties.Value; + } + + private static (WPos Center, float HalfHeight, float HalfWidth) CalculateUnionProperties2(IEnumerable bounds) + { + float totalWeight = 0; + WPos weightedSum = new(0, 0); + float maxX = float.MinValue, minX = float.MaxValue, maxZ = float.MinValue, minZ = float.MaxValue; + + foreach (var bound in bounds) + { + float weight = bound.HalfSize * bound.HalfSize; + weightedSum += bound.Center * weight; + totalWeight += weight; + + var boundPoly = bound.BuildClipPoly().ToList(); + foreach (var point in boundPoly) + { + if (point.X > maxX) maxX = point.X; + if (point.X < minX) minX = point.X; + if (point.Z > maxZ) maxZ = point.Z; + if (point.Z < minZ) minZ = point.Z; + } + } + + float halfWidth = (maxX - minX) / 2; + float halfHeight = (maxZ - minZ) / 2; + WPos center = new((minX + maxX) / 2, (minZ + maxZ) / 2); + + return (center, halfHeight, halfWidth); + } + + public override IEnumerable BuildClipPoly(float offset = 0) + { + if (_polyCache.TryGetValue(_boundsList, out var cachedResult)) + return cachedResult; + + var result = new List(); + var clipper = new ClipperLib.Clipper(0); + foreach (var bound in _boundsList) + { + var poly = bound.BuildClipPoly(offset).ToList(); + var clipperPoly = poly.Select(p => new ClipperLib.IntPoint(p.X * ScalingFactor, p.Z * ScalingFactor)).ToList(); + + if (!ClipperLib.Clipper.Orientation(clipperPoly)) + clipperPoly.Reverse(); + + clipper.AddPath(clipperPoly, ClipperLib.PolyType.ptSubject, true); + } + + var combined = new ClipperLib.PolyTree(); + clipper.Execute(ClipperLib.ClipType.ctUnion, combined, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero); + + var polygons = ClipperLib.Clipper.ClosedPathsFromPolyTree(combined); + foreach (var solPoly in polygons) + { + var polyResult = solPoly.Select(intPoint => new WPos(intPoint.X / ScalingFactor, intPoint.Y / ScalingFactor)).ToList(); + if (polyResult.First().Equals(polyResult.Last())) + polyResult.RemoveAt(polyResult.Count - 1); + + result.AddRange(polyResult); + result.Add(polyResult.First()); + } + + _polyCache[_boundsList] = result; + return result; + } + + public override Pathfinding.Map BuildMap(float resolution) + { + if (_buildMapCache.TryGetValue(_boundsList, out var cachedResult)) + return cachedResult; + var unionProperties = CalculateUnionProperties(); + var map = new Pathfinding.Map(resolution, unionProperties.Center, unionProperties.HalfWidth, unionProperties.HalfHeight); + + foreach (var (x, y, center) in map.EnumeratePixels()) + { + bool isInside = false; + foreach (var bound in _boundsList) + { + if (bound.Contains(center)) + { + isInside = true; + break; + } + } + map.Pixels[y * map.Width + x].MaxG = isInside ? float.MaxValue : 0; + } + _buildMapCache[_boundsList] = map; + return map; + } + + public override bool Contains(WPos position) + { + if (_containsCache.TryGetValue(position, out var cachedResult)) + return cachedResult; + var combinedPoly = BuildClipPoly().ToList(); + var clipperPoly = combinedPoly.Select(p => new ClipperLib.IntPoint(p.X * ScalingFactor, p.Z * ScalingFactor)).ToList(); + + if (!clipperPoly.First().Equals(clipperPoly.Last())) + clipperPoly.Add(clipperPoly.First()); + + var intPoint = new ClipperLib.IntPoint(position.X * ScalingFactor, position.Z * ScalingFactor); + var result = ClipperLib.Clipper.PointInPolygon(intPoint, clipperPoly) == 1; + _containsCache[position] = result; + return result; + } + + public override float IntersectRay(WPos origin, WDir dir) + { + if (_intersectRayCache.TryGetValue((origin, dir), out var cachedResult)) + return cachedResult; + + float nearestIntersection = float.MaxValue; + + var clipper = new ClipperLib.Clipper(0); + foreach (var bound in _boundsList) + { + var poly = bound.BuildClipPoly().Select(p => new ClipperLib.IntPoint(p.X * ScalingFactor, p.Z * ScalingFactor)).ToList(); + if (!ClipperLib.Clipper.Orientation(poly)) + poly.Reverse(); + clipper.AddPath(poly, ClipperLib.PolyType.ptSubject, true); + } + + var solution = new ClipperLib.PolyTree(); + clipper.Execute(ClipperLib.ClipType.ctUnion, solution, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero); + + var polygons = ClipperLib.Clipper.ClosedPathsFromPolyTree(solution); + foreach (var poly in polygons) + { + for (int i = 0; i < poly.Count; i++) + { + ClipperLib.IntPoint currentPoint = poly[i]; + ClipperLib.IntPoint nextPoint = poly[(i + 1) % poly.Count]; + WPos p1 = new(currentPoint.X / ScalingFactor, currentPoint.Y / ScalingFactor); + WPos p2 = new(nextPoint.X / ScalingFactor, nextPoint.Y / ScalingFactor); + + float distance = Intersect.RaySegment(origin, dir, p1, p2); + if (distance >= 0 && distance < nearestIntersection) + nearestIntersection = distance; + } + } + + _intersectRayCache[(origin, dir)] = nearestIntersection; + return nearestIntersection; + } + + public override WDir ClampToBounds(WDir offset, float scale = 1) + { + WPos position = Center + offset; + if (_clampToBoundsCache.TryGetValue((position, offset), out var cachedResult)) + return cachedResult; + + var clipper = new ClipperLib.Clipper(0); + foreach (var bound in _boundsList) + { + var poly = bound.BuildClipPoly().Select(p => new ClipperLib.IntPoint(p.X * ScalingFactor, p.Z * ScalingFactor)).ToList(); + if (!ClipperLib.Clipper.Orientation(poly)) + poly.Reverse(); + clipper.AddPath(poly, ClipperLib.PolyType.ptSubject, true); + } + + var solution = new ClipperLib.PolyTree(); + clipper.Execute(ClipperLib.ClipType.ctUnion, solution, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero); + + if (!Contains(position)) + { + float closestDist = float.MaxValue; + WPos closestPoint = default; + + var polygons = ClipperLib.Clipper.ClosedPathsFromPolyTree(solution); + foreach (var poly in polygons) + { + for (int i = 0; i < poly.Count; i++) + { + WPos p1 = new(poly[i].X / ScalingFactor, poly[i].Y / ScalingFactor); + WPos p2 = new(poly[(i + 1) % poly.Count].X / ScalingFactor, poly[(i + 1) % poly.Count].Y / ScalingFactor); + + WPos currentClosest = Intersect.ClosestPointOnSegment(p1, p2, position); + float currentDist = (currentClosest - position).Length(); + + if (currentDist < closestDist) + { + closestDist = currentDist; + closestPoint = currentClosest; + } + } + } + return closestPoint - Center; + } + _clampToBoundsCache[(position, offset)] = offset; + return offset; + } +} diff --git a/BossMod/BossModule/MiniArena.cs b/BossMod/BossModule/MiniArena.cs index 571cd0bce1..0c4de8772d 100644 --- a/BossMod/BossModule/MiniArena.cs +++ b/BossMod/BossModule/MiniArena.cs @@ -7,11 +7,10 @@ namespace BossMod; // rotation 0 corresponds to South, and increases counterclockwise (so East is +pi/2, North is pi, West is -pi/2) // - camera azimuth 0 correpsonds to camera looking North and increases counterclockwise // - screen coordinates - X points left to right, Y points top to bottom -public class MiniArena +public class MiniArena(BossModuleConfig config, ArenaBounds bounds) { - public BossModuleConfig Config { get; init; } - public ArenaBounds Bounds; - + public BossModuleConfig Config { get; init; } = config; + public ArenaBounds Bounds = bounds; public float ScreenHalfSize => 150 * Config.ArenaScale; public float ScreenMarginSize => 20 * Config.ArenaScale; @@ -21,12 +20,6 @@ public class MiniArena private float _cameraSinAzimuth = 0; private float _cameraCosAzimuth = 1; - public MiniArena(BossModuleConfig config, ArenaBounds bounds) - { - Config = config; - Bounds = bounds; - } - // prepare for drawing - set up internal state, clip rect etc. public void Begin(float cameraAzimuthRadians) { @@ -50,9 +43,17 @@ public void Begin(float cameraAzimuthRadians) ImGui.GetWindowDrawList().PushClipRect(Vector2.Max(cursor, wmin), Vector2.Min(cursor + fullSize, wmax)); if (Config.OpaqueArenaBackground) { - foreach (var p in Bounds.ClipPoly) - PathLineTo(p); + if (Bounds is ArenaBoundsPolygon or ArenaBoundsDonut or ArenaBoundsUnion) //only use the more expensive fill algorithm if needed, draw time is 0.05 to 0.1ms higher + { + var clipPoly = Bounds.BuildClipPoly(); + var triangles = Bounds.ClipAndTriangulate(clipPoly); + Zone(triangles, ArenaColor.Background); + } + else + foreach (var p in Bounds.ClipPoly) + PathLineTo(p); PathFillConvex(ArenaColor.Background); + } } @@ -201,11 +202,63 @@ public void TextWorld(WPos center, string text, uint color, float fontSize = 17) // draw arena border public void Border(uint color) { - foreach (var p in Bounds.ClipPoly) - PathLineTo(p); + if (Bounds is ArenaBoundsUnion union) + { + var polygons = union.BuildClipPoly().ToList(); + var groupedPolygons = GroupPolygons(polygons); + foreach (var polygon in groupedPolygons) + DrawPolygon(polygon, color); + } + else if (Bounds is ArenaBoundsDonut donut) + { + AddCircle(Bounds.Center, donut.OuterRadius, color, 2); + AddCircle(Bounds.Center, donut.InnerRadius, color, 2); + } + else + { + foreach (var p in Bounds.ClipPoly) + PathLineTo(p); + PathStroke(true, color, 2); + } + } + + private void DrawPolygon(IEnumerable vertices, uint color) + { + var lastPoint = vertices.First(); + PathLineTo(lastPoint); + + foreach (var point in vertices.Skip(1)) + { + PathLineTo(point); + lastPoint = point; + } PathStroke(true, color, 2); } + private static IEnumerable> GroupPolygons(IEnumerable vertices) + { + List currentPolygon = []; + WPos? firstPoint = null; + + foreach (var vertex in vertices) + { + if (vertex == firstPoint && currentPolygon.Count > 0) + { + yield return currentPolygon; + currentPolygon = []; + firstPoint = null; + } + else + { + if (firstPoint == null) + firstPoint = vertex; + currentPolygon.Add(vertex); + } + } + if (currentPolygon.Count > 0) + yield return currentPolygon; + } + public void CardinalNames() { var offCenter = ScreenHalfSize + ScreenMarginSize / 2; diff --git a/BossMod/Components/Knockback.cs b/BossMod/Components/Knockback.cs index 4cfae38bba..2b0e5e2a4a 100644 --- a/BossMod/Components/Knockback.cs +++ b/BossMod/Components/Knockback.cs @@ -3,7 +3,7 @@ namespace BossMod.Components; // generic knockback/attract component; it's a cast counter for convenience -public abstract class Knockback(BossModule module, ActionID aid = new(), bool ignoreImmunes = false, int maxCasts = int.MaxValue, bool stopAtWall = false, bool stopAfterWall = false) : CastCounter(module, aid) +public abstract class Knockback(BossModule module, ActionID aid = new(), bool ignoreImmunes = false, int maxCasts = int.MaxValue, bool stopAtWall = false, bool stopAfterWall = false, IEnumerable? safeWalls = null) : CastCounter(module, aid) { public enum Kind { @@ -25,6 +25,17 @@ public record struct Source( float MinDistance = 0 // irrelevant for knockbacks ); + public record struct SafeWall( + // for line segments + WPos Vertex1 = default, + WPos Vertex2 = default, + // for circle segments + WPos Center = default, + float Radius = default, + Angle StartAngle = default, + Angle EndAngle = default + ); + protected struct PlayerImmuneState { public DateTime RoleBuffExpire; // 0 if not active @@ -34,6 +45,7 @@ protected struct PlayerImmuneState public readonly bool ImmuneAt(DateTime time) => RoleBuffExpire > time || JobBuffExpire > time || DutyBuffExpire > time; } + public IEnumerable SafeWalls { get; init; } = safeWalls ?? []; public bool IgnoreImmunes { get; init; } = ignoreImmunes; public bool StopAtWall = stopAtWall; // use if wall is solid rather than deadly public bool StopAfterWall = stopAfterWall; // use if the wall is a polygon where you need to check for intersections @@ -153,14 +165,49 @@ public override void OnStatusLose(Actor actor, ActorStatus status) var distance = s.Distance; if (s.Kind is Kind.TowardsOrigin) - distance = Math.Min(s.Distance, (s.Origin - from).Length() - s.MinDistance); + distance = MathF.Min(s.Distance, (s.Origin - from).Length() - s.MinDistance); if (distance <= 0) continue; // this could happen if attract starts from < min distance if (StopAtWall) - distance = Math.Min(distance, Module.Bounds.IntersectRay(from, dir) - actor.HitboxRadius); + distance = MathF.Min(distance, Module.Bounds.IntersectRay(from, dir) - actor.HitboxRadius); if (StopAfterWall) - distance = Math.Min(distance, Module.Bounds.IntersectRay(from, dir) + 0.001f); + distance = Math.Min(distance, Module.Bounds.IntersectRay(from, dir) + 0.1f); + + if (SafeWalls.Any()) + { + float distanceToWall = float.MaxValue; + foreach (var wall in SafeWalls) + { + float t = float.MaxValue; + + if (wall.Vertex1 != default && wall.Vertex2 != default) // Line segment + t = Intersect.RaySegment(from, dir, wall.Vertex1, wall.Vertex2); + else if (wall.Center != default && wall.Radius != 0) // Circle segment + { + t = Intersect.RayCircle(from, dir, wall.Center, wall.Radius); + if (t < float.MaxValue) + { + WPos intersection = from + t * dir; + Angle intersectionAngle = Angle.FromDirection(intersection - wall.Center).Normalized(); + + bool isWithinAngles; + if (wall.StartAngle <= wall.EndAngle) + isWithinAngles = intersectionAngle >= wall.StartAngle && intersectionAngle <= wall.EndAngle; + else // Angle wraps around at 0° + isWithinAngles = intersectionAngle >= wall.StartAngle || intersectionAngle <= wall.EndAngle; + if (!isWithinAngles) + t = float.MaxValue; + } + } + if (t < distanceToWall && t <= s.Distance) + distanceToWall = t; + } + if (distanceToWall < float.MaxValue) + distance = Math.Min(distance, distanceToWall - actor.HitboxRadius); + else + distance = Math.Min(distance, Module.Bounds.IntersectRay(from, dir) + 0.1f); + } var to = from + distance * dir; yield return (from, to); from = to; @@ -173,8 +220,8 @@ public override void OnStatusLose(Actor actor, ActorStatus status) // generic 'knockback from/attract to cast target' component // TODO: knockback is really applied when effectresult arrives rather than when actioneffect arrives, this is important for ai hints (they can reposition too early otherwise) -public class KnockbackFromCastTarget(BossModule module, ActionID aid, float distance, bool ignoreImmunes = false, int maxCasts = int.MaxValue, AOEShape? shape = null, Kind kind = Kind.AwayFromOrigin, float minDistance = 0, bool minDistanceBetweenHitboxes = false, bool stopAtWall = false, bool stopAfterWall = false) - : Knockback(module, aid, ignoreImmunes, maxCasts, stopAtWall, stopAfterWall) +public class KnockbackFromCastTarget(BossModule module, ActionID aid, float distance, bool ignoreImmunes = false, int maxCasts = int.MaxValue, AOEShape? shape = null, Kind kind = Kind.AwayFromOrigin, float minDistance = 0, bool minDistanceBetweenHitboxes = false, bool stopAtWall = false, bool stopAfterWall = false, IEnumerable? safeWalls = null) + : Knockback(module, aid, ignoreImmunes, maxCasts, stopAtWall, stopAfterWall, safeWalls) { public float Distance = distance; public AOEShape? Shape = shape; diff --git a/BossMod/Components/LineOfSightAOE.cs b/BossMod/Components/LineOfSightAOE.cs index c8b630ab13..94e5233ee7 100644 --- a/BossMod/Components/LineOfSightAOE.cs +++ b/BossMod/Components/LineOfSightAOE.cs @@ -44,7 +44,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { // inverse of a union of inverted max-range circle and a bunch of infinite cones minus inner cirles var normals = Visibility.Select(v => (v.Distance, (v.Dir + v.HalfWidth).ToDirection().OrthoL(), (v.Dir - v.HalfWidth).ToDirection().OrthoR())).ToArray(); - Func invertedDistanceToSafe = p => + float invertedDistanceToSafe(WPos p) { var off = p - Origin.Value; var distOrigin = off.Length(); @@ -58,7 +58,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme distanceToSafe = Math.Min(distanceToSafe, distCone); } return -distanceToSafe; - }; + } hints.AddForbiddenZone(invertedDistanceToSafe, NextExplosion); } if (BlockersImpassable) @@ -83,7 +83,7 @@ public override void DrawArenaBackground(int pcSlot, Actor pc) // simple line-of-sight aoe that happens at the end of the cast public abstract class CastLineOfSightAOE : GenericLineOfSightAOE { - private readonly List _casters = new(); + private readonly List _casters = []; public Actor? ActiveCaster => _casters.MinBy(c => c.CastInfo!.NPCFinishAt); public CastLineOfSightAOE(BossModule module, ActionID aid, float maxRange, bool blockersImpassable) : base(module, aid, maxRange, blockersImpassable) diff --git a/BossMod/Modules/DemoModule.cs b/BossMod/Modules/DemoModule.cs index d842400877..ad3211f72c 100644 --- a/BossMod/Modules/DemoModule.cs +++ b/BossMod/Modules/DemoModule.cs @@ -8,6 +8,7 @@ public override void AddHints(int slot, Actor actor, TextHints hints) { hints.Add("Hint", false); hints.Add("Risk"); + } public override void AddMovementHints(int slot, Actor actor, MovementHints movementHints) @@ -22,7 +23,7 @@ public override void AddGlobalHints(GlobalHints hints) public override void DrawArenaBackground(int pcSlot, Actor pc) { - Arena.ZoneCircle(Module.Bounds.Center, 10, ArenaColor.AOE); + Arena.ZoneCircle(Module.Bounds.Center, 12, ArenaColor.AOE); } public override void DrawArenaForeground(int pcSlot, Actor pc) @@ -31,8 +32,30 @@ public override void DrawArenaForeground(int pcSlot, Actor pc) } } + //for testing ray intersections with new arena shapes + // class RayIntersectionTest(BossModule module) : Components.Knockback(module) + // { + + // public override IEnumerable Sources(int slot, Actor actor) + // { + // StopAfterWall = true; + // yield return new(Module.Bounds.Center, 100); + // } + // } + + // various arena bounds for testing + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsUnion([new ArenaBoundsDonut(new(80, 100), 20,30), new ArenaBoundsDonut(new(120, 120), 20,30)])) + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsUnion([new ArenaBoundsDonut(new(80, 100), 20,30), new ArenaBoundsDonut(new(120, 120), 20,30), new ArenaBoundsDonut(new(100, 100), 20,30)])) + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsUnion([new ArenaBoundsCircle(new(105, 115), 15), new ArenaBoundsCircle(new(120, 120), 15), new ArenaBoundsCircle(new(120, 100), 15)])) + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsUnion([new ArenaBoundsDonut(new(100, 100), 20,30), new ArenaBoundsRect(new(120, 120), 5,20,240.Degrees()), new ArenaBoundsRect(new(80, 80), 5,20,-120.Degrees()), new ArenaBoundsRect(new(80, 120), 5,20,120.Degrees())])) + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsDonut(new(100, 100), 20, 30)) + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsUnion([new ArenaBoundsCircle(new(105, 115), 10), new ArenaBoundsCircle(new(140, 100), 10), new ArenaBoundsCircle(new(120, 95), 10)])) + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsUnion([new ArenaBoundsDonut(new(105, 115), 5,10), new ArenaBoundsDonut(new(120, 100), 5,10)])) + // public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsUnion([new ArenaBoundsDonut(new(80, 100), 10,20), new ArenaBoundsDonut(new(120, 120), 10,20), new ArenaBoundsCircle(new(100, 100), 30)])) public DemoModule(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(100, 100), 20)) { ActivateComponent(); + // ActivateComponent(); } + } diff --git a/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/A12Rhalgr.cs b/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/A12Rhalgr.cs index 5002b1b6b7..2cb84685fe 100644 --- a/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/A12Rhalgr.cs +++ b/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/A12Rhalgr.cs @@ -7,10 +7,11 @@ class StrikingMeteor(BossModule module) : Components.LocationTargetedAOEs(module [ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 866, NameID = 11273, SortOrder = 3)] public class A12Rhalgr(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsPolygon(arenacoords)) { - private static readonly List arenacoords = [new (-29.2f, 235.5f), new (-40.3f, 248f), new (-47.5f, 260f), new (-53.8f, 273.7f), new (-46.4f, 276f), new (-45.1f, 274.8f), - new (-43.2f, 272.1f), new (-40.4f, 270.7f), new (-38.8f, 271.4f), new (-38.3f, 272.6f), new (-38.2f, 275f), new (-39.1f, 278.5f), new (-40.7f, 282.4f), new (-46.1f, 291.3f), - new (-49.2f, 296.8f), new (-41f, 300.2f), new (-37.1f, 293.4f), new (-34.9f, 291f), new (-32.5f, 290.2f), new (-30.7f, 291.1f), new (-30.5f, 295.8f), new (-31.2f, 305f), - new (-22.6f, 306f), new (-19.8f, 290.5f), new (-18f, 288.7f), new (-16f, 289.2f), new (-14f, 290.9f), new (-13.7f, 303.7f), new (-6.3f, 304.7f), new (-4.5f, 288.2f), - new (-3.7f, 287f), new (-1.3f, 287.8f), new (-0.1f, 289.2f), new (3.4f, 297.15f), new (8.9f, 294f), new (6.4f, 286.6f), new (6.2f, 283.2f), new (7.3f, 276.4f), - new (7.7f, 267.2f), new (6.8f, 253f), new (4.5f, 242.7f), new (2.23f, 235.6f)]; + private static readonly List arenacoords = [new(-29.2f, 235.5f), new(-40.3f, 248f), new(-47.5f, 260), new(-52.64f, 274.2f), new(-46.3f, 276.04f), new(-45.1f, 274.8f), + new(-43.2f, 272.1f), new(-40.4f, 270.7f), new(-38.8f, 271.4f), new(-38.3f, 272.6f), new(-38.2f, 275f), new(-39.1f, 278.5f), new(-40.7f, 282.4f), new(-46.1f, 291.3f), + new(-49.39f, 296.73f), new(-40.96f, 300.2f), new(-37.1f, 293.4f), new(-34.9f, 291f), new(-32.5f, 290.2f), new(-30.7f, 291.1f), new(-30.5f, 295.8f), new(-31.3f, 304.94f), + new(-22.35f, 306.16f), new(-19.8f, 290.5f), new(-18f, 288.7f), new(-16f, 289.2f), new(-14f, 290.9f), new(-13.9f, 303.98f), new(-6.23f, 304.72f), new(-4.5f, 288.2f), + new(-3.7f, 287f), new(-1.3f, 287.8f), new(-0.1f, 289.2f), new(3.31f, 297.2f), new(9.09f, 293.91f), new(6.4f, 286.6f), new(6.2f, 283.2f), new(7.3f, 276.4f), + new(7.7f, 267.2f), new(6.8f, 253f), new(4.5f, 242.7f), new(2.23f, 235.6f)]; } + diff --git a/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/RhalgrBeacon.cs b/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/RhalgrBeacon.cs index b3087eb9d2..3ad5c334a3 100644 --- a/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/RhalgrBeacon.cs +++ b/BossMod/Modules/Endwalker/Alliance/A12Rhalgr/RhalgrBeacon.cs @@ -19,11 +19,7 @@ public override void OnActorCreated(Actor actor) // TODO: this is a knockback 50, ignores immunities - but need to clamp to correct fingers // there are two possible source locations ([-10.12, 268.50] and [-24.12, 266.50]), two potential fingers for each - one of them is sometimes covered by lightning aoes -class RhalgrBeaconKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.RhalgrsBeaconKnockback), 50, true, stopAfterWall: true) +class RhalgrBeaconKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.RhalgrsBeaconKnockback), 50, true, stopAfterWall: true, safeWalls: safewalls) { - public override void AddHints(int slot, Actor actor, TextHints hints) - { - if (Sources(slot, actor).Any()) - hints.Add("Get knocked to a correct finger!"); - } + private static readonly List safewalls = [new (new(9.09f, 293.91f), new(3.31f, 297.2f)), new(new(-6.23f, 304.72f), new(-13.9f, 303.98f)), new(new(-22.35f, 306.16f), new(-31.3f, 304.94f)), new(new(-40.96f, 300.2f), new(-49.39f, 296.73f)), new(new(-46.3f, 276.04f), new(-52.64f, 274.2f))]; } diff --git a/BossMod/Modules/Endwalker/Alliance/A34Eulogia/EulogiaQuintessence.cs b/BossMod/Modules/Endwalker/Alliance/A34Eulogia/EulogiaQuintessence.cs index b174d88f9c..acbceffc58 100644 --- a/BossMod/Modules/Endwalker/Alliance/A34Eulogia/EulogiaQuintessence.cs +++ b/BossMod/Modules/Endwalker/Alliance/A34Eulogia/EulogiaQuintessence.cs @@ -72,7 +72,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) _aoes.Add(new(donut, position, default, _activation)); if ((AID)spell.Action.ID is AID.SecondFormRight or AID.ThirdFormRight) { - if (_index == 0x52) //replay for proof of index 51 still missing + if (_index is 0x52 or 0x51) //replay for proof of index 51 still missing _aoes.Add(new(cone, position, _rot3, _activation)); if (_index is 0x57 or 0x50) _aoes.Add(new(cone, position, _rot2, _activation)); diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D112GalateaMagna.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D112GalateaMagna.cs index 86776f3f5c..71c2b39045 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D112GalateaMagna.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D112GalateaMagna.cs @@ -203,7 +203,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { if (Towers.Count > 0) - hints.AddForbiddenZone(ShapeDistance.InvertedCircle(Towers[0].Position, 6)); + hints.AddForbiddenZone(ShapeDistance.InvertedCircle(Towers[0].Position, 5)); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs index f6fc96e826..c0909ec6cb 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs @@ -26,58 +26,6 @@ public enum AID : uint Wallop = 33346, // MammothTentacle->self, 3.0s cast, range 22 width 8 rect } -class Border(BossModule module) : BossComponent(module) -{ - private static readonly float _platformOffset = 25; - private static readonly float _platformRadius = 8; - private static readonly Angle[] _platformDirections = [-90.Degrees(), -45.Degrees(), 0.Degrees(), 45.Degrees(), 90.Degrees()]; - private static readonly WDir[] _platformCenters = _platformDirections.Select(d => _platformOffset * d.ToDirection()).ToArray(); - - private static readonly float _bridgeInner = 22; - private static readonly float _bridgeOuter = 26; - private static readonly Angle _offInner = DirToPointAtDistance(_bridgeInner); - private static readonly Angle _offOuter = DirToPointAtDistance(_bridgeOuter); - - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) - { - hints.AddForbiddenZone(p => - { - // union of platforms - var res = _platformCenters.Select(off => ShapeDistance.Circle(Module.Bounds.Center + off, _platformRadius)(p)).Min(); - // union of bridges - for (int i = 1; i < 5; ++i) - res = Math.Min(res, ShapeDistance.Rect(Module.Bounds.Center + _platformCenters[i - 1], Module.Bounds.Center + _platformCenters[i], 3)(p)); - // invert - return -res; - }); - - base.AddAIHints(slot, actor, assignment, hints); - } - - public override void DrawArenaForeground(int pcSlot, Actor pc) - { - // draw platforms - foreach (var c in _platformCenters) - Arena.AddCircle(Module.Bounds.Center + c, _platformRadius, ArenaColor.Border); - - // draw bridges - for (int i = 1; i < 5; ++i) - { - DrawBridgeLine(_platformDirections[i - 1], _platformDirections[i], _offInner, _bridgeInner); - DrawBridgeLine(_platformDirections[i - 1], _platformDirections[i], _offOuter, _bridgeOuter); - } - } - - private static Angle DirToPointAtDistance(float d) => Angle.Acos((_platformOffset * _platformOffset + d * d - _platformRadius * _platformRadius) / (2 * _platformOffset * d)); - - private void DrawBridgeLine(Angle from, Angle to, Angle offset, float distance) - { - var p1 = Module.Bounds.Center + distance * (from + offset).ToDirection(); - var p2 = Module.Bounds.Center + distance * (to - offset).ToDirection(); - Arena.AddLine(p1, p2, ArenaColor.Border); - } -} - class Wallop(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Wallop), new AOEShapeRect(22, 4)); class VividEyes(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.VividEyes), new AOEShapeDonut(20, 26)); class Clearout(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Clearout), new AOEShapeCone(16, 60.Degrees())); @@ -106,10 +54,8 @@ public D123OctomammothStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "dhoggpt, Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 822, NameID = 12334)] -class D123Octomammoth : BossModule -{ - public D123Octomammoth(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-370, -368), 33.3f)) - { - ActivateComponent(); - } -} +class D123Octomammoth(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsUnion([new ArenaBoundsRect(new(-347.74f, -358.78f), 2, 10, -22.5f.Degrees()), + new ArenaBoundsRect(new(-360.78f, -345.74f), 2, 10, -67.5f.Degrees()), new ArenaBoundsRect(new(-392.26f, -358.78f), 2, 10, 22.5f.Degrees()), + new ArenaBoundsRect(new(-379.22f, -345.74f), 2, 10, 67.5f.Degrees()), new ArenaBoundsCircle(new(-345, -368), 8), new ArenaBoundsCircle(new(-387.678f, -350.322f), 8), + new ArenaBoundsCircle(new(-352.322f, -350.322f), 8), new ArenaBoundsCircle(new(-370, -343), 8), new ArenaBoundsCircle(new(-395, -368), 8)])); + diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouAcheloios.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouAcheloios.cs index 49e503c2f0..bf97c271d4 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouAcheloios.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouAcheloios.cs @@ -139,7 +139,7 @@ public AcheloiosStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12019)] -public class Acheloios(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Acheloios(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouLeon.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouLeon.cs index 9fb9e4aa9c..cd36729250 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouLeon.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouLeon.cs @@ -47,7 +47,7 @@ public LeonStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 11997)] -public class Leon(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Leon(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMandragoras.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMandragoras.cs index 112f248658..0b2e121675 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMandragoras.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMandragoras.cs @@ -53,7 +53,7 @@ public MandragorasStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12022)] -public class Mandragoras(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Mandragoras(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs index a9e3c25ada..be7f940a2d 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs @@ -84,7 +84,7 @@ public MegakanthaStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12009)] -public class Megakantha(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Megakantha(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs index 6007efb977..a6e20d48e9 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs @@ -84,7 +84,7 @@ public MeganereisStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12014)] -public class Meganereis(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Meganereis(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs index 3ae17802d1..dad6c12502 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs @@ -60,7 +60,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { base.AddAIHints(slot, actor, assignment, hints); if (target == actor && targeted) - hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 18)); + hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 17.5f)); } public override void AddHints(int slot, Actor actor, TextHints hints) @@ -94,7 +94,7 @@ public PithekosStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12001)] -public class Pithekos(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Pithekos(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSatyros.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSatyros.cs index f04e65aae7..d6afadb0a8 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSatyros.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSatyros.cs @@ -52,7 +52,7 @@ public SatyrosStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12003)] -public class Satyros(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Satyros(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs index ab6bab4aaa..0fca0d8187 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs @@ -78,7 +78,7 @@ public SphinxStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12016)] -public class Sphinx(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Sphinx(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs index 546ef5ec23..7114c3f938 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs @@ -96,7 +96,7 @@ public StyphnolobionStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12012)] -public class Styphnolobion(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Styphnolobion(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouTigris.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouTigris.cs index 208456d9f1..060a93a92e 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouTigris.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouTigris.cs @@ -64,7 +64,7 @@ public TigrisStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 11999)] -public class Tigris(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Tigris(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LampasChrysine.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LampasChrysine.cs index 94e46c12ee..8585d14f2b 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LampasChrysine.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LampasChrysine.cs @@ -55,7 +55,7 @@ public LampasStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12021)] -public class Lampas(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Lampas(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs index 694683a114..1f56eace7e 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs @@ -87,7 +87,7 @@ class HeavySmash(BossModule module) : Components.StackWithCastTargets(module, Ac class IcePillarSpawn(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = new(); + private readonly List _aoes = []; public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes.Take(4); @@ -124,7 +124,7 @@ public LyssaStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12024)] -public class Lyssa(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Lyssa(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs index 0121d1cc45..e2630734f7 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs @@ -112,7 +112,7 @@ public NarkissosStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 909, NameID = 12029)] -public class Narkissos(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Narkissos(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Global/MaskedCarnivale/Stage29RedFraughtAndBlue/Stage29Act2.cs b/BossMod/Modules/Global/MaskedCarnivale/Stage29RedFraughtAndBlue/Stage29Act2.cs index 910496f9cf..0cfefe3b76 100644 --- a/BossMod/Modules/Global/MaskedCarnivale/Stage29RedFraughtAndBlue/Stage29Act2.cs +++ b/BossMod/Modules/Global/MaskedCarnivale/Stage29RedFraughtAndBlue/Stage29Act2.cs @@ -166,7 +166,7 @@ public Stage29Act2States(BossModule module) : base(module) } } -[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.MaskedCarnivale, GroupID = 698, NameID = 9241, SortOrder = 1)] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.MaskedCarnivale, GroupID = 698, NameID = 9241, SortOrder = 2)] public class Stage29Act2 : BossModule { public Stage29Act2(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(100, 100), 16)) diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs index d0a8e866a9..1e7941161d 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs @@ -74,7 +74,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { base.AddAIHints(slot, actor, assignment, hints); if (target == actor && targeted) - hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 18)); + hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 17.5f)); } } @@ -127,7 +127,7 @@ public AiravataStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7601)] -public class Airavata(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Airavata(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs index b5a27161ac..29a9ed637a 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs @@ -59,7 +59,7 @@ public ArachneStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7623)] -public class Arachne(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Arachne(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs index 0565db71b2..2867ecf115 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs @@ -76,7 +76,7 @@ public BeastStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7588)] -public class Beast(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Beast(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs index b95732b558..bfb01a190d 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs @@ -70,7 +70,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { base.AddAIHints(slot, actor, assignment, hints); if (target == actor && targeted) - hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 18)); + hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 17.5f)); } public override void AddHints(int slot, Actor actor, TextHints hints) @@ -109,7 +109,7 @@ public ChimeraStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7591)] -public class Chimera(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Chimera(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs index 03a8c7a0c2..f99cb3c658 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs @@ -74,7 +74,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { base.AddAIHints(slot, actor, assignment, hints); if (target == actor && targeted) - hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 18)); + hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 17.5f)); } public override void AddHints(int slot, Actor actor, TextHints hints) @@ -111,7 +111,7 @@ public DiresaurStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7627)] -public class Diresaur(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Diresaur(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs index 9e64c9db20..265f1f7a8b 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs @@ -81,7 +81,7 @@ public DullahanStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7585)] -public class Dullahan(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Dullahan(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs index 56992ee276..16ca290c87 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs @@ -111,7 +111,7 @@ public KelpieStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7589)] -public class Kelpie(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Kelpie(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs index ec7a0d2191..6921893560 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs @@ -58,7 +58,7 @@ public MandragoraStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7600)] -public class Mandragora(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Mandragora(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs index 82d19d34b7..0003457d95 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs @@ -36,7 +36,7 @@ public SkateneStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7587)] -public class Skatene(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Skatene(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs index f71475cee8..6ae4ca457d 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs @@ -69,7 +69,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme { base.AddAIHints(slot, actor, assignment, hints); if (target == actor && targeted) - hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 18)); + hints.AddForbiddenZone(ShapeDistance.Circle(Module.Bounds.Center, 17.5f)); } public override void AddHints(int slot, Actor actor, TextHints hints) @@ -104,7 +104,7 @@ public TotemStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7586)] -public class Totem(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Totem(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/Hati.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/Hati.cs index 410f742d65..5ca7028d43 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/Hati.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/Hati.cs @@ -5,18 +5,31 @@ public enum OID : uint Boss = 0x2538, //R=5.4 BossAdd = 0x2569, //R=3.0 BossHelper = 0x233C, + AltarQueen = 0x254A, // R0,840, icon 5, needs to be killed in order from 1 to 5 for maximum rewards + AltarGarlic = 0x2548, // R0,840, icon 3, needs to be killed in order from 1 to 5 for maximum rewards + AltarTomato = 0x2549, // R0,840, icon 4, needs to be killed in order from 1 to 5 for maximum rewards + AltarOnion = 0x2546, // R0,840, icon 1, needs to be killed in order from 1 to 5 for maximum rewards + AltarEgg = 0x2547, // R0,840, icon 2, needs to be killed in order from 1 to 5 for maximum rewards } public enum AID : uint { AutoAttack = 870, // Boss->player, no cast, single-target - AutoAttack2 = 6499, // BossAdd->player, no cast, single-target + AutoAttack2 = 872, // BonusAdds->player, no cast, single-target + AutoAttack3 = 6499, // BossAdd->player, no cast, single-target GlassyNova = 13362, // Boss->self, 3,0s cast, range 40+R width 8 rect Hellstorm = 13359, // Boss->self, 3,0s cast, single-target Hellstorm2 = 13363, // BossHelper->location, 3,5s cast, range 10 circle Netherwind = 13741, // BossAdd->self, 3,0s cast, range 15+R width 4 rect BrainFreeze = 13361, // Boss->self, 4,0s cast, range 10+R circle, turns player into Imp PolarRoar = 13360, // Boss->self, 3,0s cast, range 9-40 donut + + Pollen = 6452, // 2A0A->self, 3,5s cast, range 6+R circle + TearyTwirl = 6448, // 2A06->self, 3,5s cast, range 6+R circle + HeirloomScream = 6451, // 2A09->self, 3,5s cast, range 6+R circle + PluckAndPrune = 6449, // 2A07->self, 3,5s cast, range 6+R circle + PungentPirouette = 6450, // 2A08->self, 3,5s cast, range 6+R circle + Telega = 9630, // BonusAdds->self, no cast, single-target, bonus adds disappear } class PolarRoar(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.PolarRoar), new AOEShapeDonut(9, 40)); @@ -24,6 +37,11 @@ class Hellstorm(BossModule module) : Components.LocationTargetedAOEs(module, Act class Netherwind(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Netherwind), new AOEShapeRect(18, 2)); class GlassyNova(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.GlassyNova), new AOEShapeRect(45.4f, 4)); class BrainFreeze(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BrainFreeze), new AOEShapeCircle(15.4f)); +class PluckAndPrune(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.PluckAndPrune), new AOEShapeCircle(6.84f)); +class TearyTwirl(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.TearyTwirl), new AOEShapeCircle(6.84f)); +class HeirloomScream(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HeirloomScream), new AOEShapeCircle(6.84f)); +class PungentPirouette(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.PungentPirouette), new AOEShapeCircle(6.84f)); +class Pollen(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Pollen), new AOEShapeCircle(6.84f)); class HatiStates : StateMachineBuilder { @@ -35,18 +53,33 @@ public HatiStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.BossAdd).All(e => e.IsDead); + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.BossAdd).All(e => e.IsDead) && module.Enemies(OID.AltarEgg).All(e => e.IsDead) && module.Enemies(OID.AltarQueen).All(e => e.IsDead) && module.Enemies(OID.AltarOnion).All(e => e.IsDead) && module.Enemies(OID.AltarGarlic).All(e => e.IsDead) && module.Enemies(OID.AltarTomato).All(e => e.IsDead); } } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7590)] -public class Hati(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class Hati(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { Arena.Actor(PrimaryActor, ArenaColor.Enemy); foreach (var s in Enemies(OID.BossAdd)) Arena.Actor(s, ArenaColor.Object); + foreach (var s in Enemies(OID.AltarEgg)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarTomato)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarQueen)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarGarlic)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarOnion)) + Arena.Actor(s, ArenaColor.Vulnerable); } public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -56,6 +89,11 @@ public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.As { e.Priority = (OID)e.Actor.OID switch { + OID.AltarOnion => 7, + OID.AltarEgg => 6, + OID.AltarGarlic => 5, + OID.AltarTomato => 4, + OID.AltarQueen => 3, OID.BossAdd => 2, OID.Boss => 1, _ => 0 diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs index 23ed8d6a0f..a7f2c64761 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs @@ -33,7 +33,7 @@ public TheGreatGoldWhiskerStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7599)] -public class TheGreatGoldWhisker(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class TheGreatGoldWhisker(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs index 92b76cc6aa..288cbc9660 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs @@ -81,7 +81,7 @@ public TheOlderOneStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7597)] -public class TheOlderOne(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class TheOlderOne(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs index 7a1f8095ed..be09f183fd 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs @@ -76,7 +76,7 @@ public TheWingedStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 586, NameID = 7595)] -public class TheWinged(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) +public class TheWinged(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 19)) { protected override void DrawEnemies(int pcSlot, Actor pc) { diff --git a/BossMod/Util/Angle.cs b/BossMod/Util/Angle.cs index 2711f1a6a9..48c1ee66e7 100644 --- a/BossMod/Util/Angle.cs +++ b/BossMod/Util/Angle.cs @@ -2,18 +2,16 @@ // wrapper around float, stores angle in radians, provides type-safety and convenience // when describing rotation in world, common convention is 0 for 'south'/'down'/(0, -1) and increasing counterclockwise - so +90 is 'east'/'right'/(1, 0) -public struct Angle +public struct Angle(float radians = 0) { public const float RadToDeg = 180 / MathF.PI; public const float DegToRad = MathF.PI / 180; - public float Rad; - public float Deg => Rad * RadToDeg; - - public Angle(float radians = 0) { Rad = radians; } + public float Rad = radians; + public readonly float Deg => Rad * RadToDeg; public static Angle FromDirection(WDir dir) => new(MathF.Atan2(dir.X, dir.Z)); - public WDir ToDirection() => new(Sin(), Cos()); + public readonly WDir ToDirection() => new(Sin(), Cos()); public static Angle operator +(Angle a, Angle b) => new(a.Rad + b.Rad); public static Angle operator -(Angle a, Angle b) => new(a.Rad - b.Rad); @@ -21,14 +19,18 @@ public struct Angle public static Angle operator *(Angle a, float b) => new(a.Rad * b); public static Angle operator *(float a, Angle b) => new(a * b.Rad); public static Angle operator /(Angle a, float b) => new(a.Rad / b); - public Angle Abs() => new(Math.Abs(Rad)); - public float Sin() => MathF.Sin(Rad); - public float Cos() => MathF.Cos(Rad); - public float Tan() => MathF.Tan(Rad); + public static bool operator >(Angle a, Angle b) => a.Rad > b.Rad; + public static bool operator <(Angle a, Angle b) => a.Rad < b.Rad; + public static bool operator >=(Angle a, Angle b) => a.Rad >= b.Rad; + public static bool operator <=(Angle a, Angle b) => a.Rad <= b.Rad; + public readonly Angle Abs() => new(Math.Abs(Rad)); + public readonly float Sin() => MathF.Sin(Rad); + public readonly float Cos() => MathF.Cos(Rad); + public readonly float Tan() => MathF.Tan(Rad); public static Angle Asin(float x) => new(MathF.Asin(x)); public static Angle Acos(float x) => new(MathF.Acos(x)); - public Angle Normalized() + public readonly Angle Normalized() { var r = Rad; while (r < -MathF.PI) @@ -38,13 +40,13 @@ public Angle Normalized() return new(r); } - public bool AlmostEqual(Angle other, float epsRad) => Math.Abs((this - other).Normalized().Rad) <= epsRad; + public readonly bool AlmostEqual(Angle other, float epsRad) => Math.Abs((this - other).Normalized().Rad) <= epsRad; public static bool operator ==(Angle l, Angle r) => l.Rad == r.Rad; public static bool operator !=(Angle l, Angle r) => l.Rad != r.Rad; - public override bool Equals(object? obj) => obj is Angle && this == (Angle)obj; - public override int GetHashCode() => Rad.GetHashCode(); - public override string ToString() => Deg.ToString("f0"); + public override readonly bool Equals(object? obj) => obj is Angle angle && this == angle; + public override readonly int GetHashCode() => Rad.GetHashCode(); + public override readonly string ToString() => Deg.ToString("f0"); } public static class AngleExtensions diff --git a/BossMod/Util/Clip2D.cs b/BossMod/Util/Clip2D.cs index 8d5bd09d41..ce8cdbc3bb 100644 --- a/BossMod/Util/Clip2D.cs +++ b/BossMod/Util/Clip2D.cs @@ -19,7 +19,7 @@ public IEnumerable ClipPoly set => _clipPoly = value.Select(ConvertPoint).ToList(); } - public Clip2D(bool strictlySimple = true) + public Clip2D(bool strictlySimple = false) { _clipper = new(strictlySimple ? Clipper.ioStrictlySimple : 0); } diff --git a/BossMod/Util/ShapeDistance.cs b/BossMod/Util/ShapeDistance.cs index 31a8fa8ace..45d818df1b 100644 --- a/BossMod/Util/ShapeDistance.cs +++ b/BossMod/Util/ShapeDistance.cs @@ -24,6 +24,12 @@ public static Func Donut(WPos origin, float innerRadius, float oute }; } + public static Func InvertedDonut(WPos origin, float innerRadius, float outerRadius) + { + var donut = Donut(origin, innerRadius, outerRadius); + return p => -donut(p); + } + public static Func Cone(WPos origin, float radius, Angle centerDir, Angle halfAngle) { if (halfAngle.Rad <= 0 || radius <= 0) @@ -191,7 +197,7 @@ public static Func EquilateralTriangle(WPos origin, Angle rotation, return ConvexPolygon(vertices, cw); } -//should work with any non-self intersecting polygon + //should work for any simple (no self intersections or holes) polygon with an IEnumerable/List of points public static Func Polygon(IEnumerable vertices) { return p => diff --git a/BossMod/Util/WPosDir.cs b/BossMod/Util/WPosDir.cs index c11ed27f68..02ece835e3 100644 --- a/BossMod/Util/WPosDir.cs +++ b/BossMod/Util/WPosDir.cs @@ -24,10 +24,12 @@ public struct WDir public readonly WDir OrthoR() => new(-Z, X); // CW, same length public static float Dot(WDir a, WDir b) => a.X * b.X + a.Z * b.Z; public readonly float Dot(WDir a) => X * a.X + Z * a.Z; + public static float Cross(WDir a, WDir b) => a.X * b.Z - a.Z * b.X; public readonly float LengthSq() => X * X + Z * Z; public readonly float Length() => MathF.Sqrt(LengthSq()); public static WDir Normalize(WDir a) => a / a.Length(); public readonly WDir Normalized() => this / Length(); + public static bool AlmostZero(WDir a, float eps) => Math.Abs(a.X) <= eps && Math.Abs(a.Z) <= eps; public readonly bool AlmostZero(float eps) => AlmostZero(this, eps); public static bool AlmostEqual(WDir a, WDir b, float eps) => AlmostZero(a - b, eps); @@ -40,26 +42,6 @@ public struct WDir public override readonly string ToString() => $"({X:f3}, {Z:f3})"; } -public static class Extensions -{ - public static float Cross(this WDir a, WDir b) - { - return a.X * b.Z - a.Z * b.X; - } - - public static float AngleBetween(this WDir v1, WDir v2) - { - var dot = v1.Dot(v2); - var det = v1.X * v2.Z - v1.Z * v2.X; - return MathF.Atan2(det, dot); - } - - public static float Normalized(this float value) - { - return value / MathF.Sqrt(value * value); - } -} - // 2d vector that represents world-space position on XZ plane public struct WPos { @@ -73,6 +55,7 @@ public struct WPos public static WPos operator *(WPos a, float b) => new (a.X * b, a.Z * b); public static WPos operator +(WPos a, float b) => new(a.X + b, a.Z + b); public static WPos operator /(WPos a, int b) => new (a.X / b, a.Z / b); + public static WPos operator /(WPos a, float b) => new (a.X / b, a.Z / b); public static WPos operator +(WPos a, WPos b) => new(a.X + b.X, a.Z + b.Z); public static WPos operator +(WPos a, WDir b) => new(a.X + b.X, a.Z + b.Z); public static WPos operator +(WDir a, WPos b) => new(a.X + b.X, a.Z + b.Z); @@ -81,7 +64,8 @@ public struct WPos public static bool AlmostEqual(WPos a, WPos b, float eps) => (a - b).AlmostZero(eps); public readonly bool AlmostEqual(WPos b, float eps) => (this - b).AlmostZero(eps); public static WPos Lerp(WPos from, WPos to, float progress) => new(from.ToVec2() * (1 - progress) + to.ToVec2() * progress); - + public static float Dot(WPos a, WPos b) => a.X * b.X + a.Z * b.Z; + public static WPos Cross(WPos a, WPos b) => new(a.Z * b.Z - a.X * b.X, a.X * b.X - a.Z * b.Z); public static bool operator ==(WPos l, WPos r) => l.X == r.X && l.Z == r.Z; public static bool operator !=(WPos l, WPos r) => l.X != r.X || l.Z != r.Z; public override readonly bool Equals(object? obj) => obj is WPos pos && this == pos; @@ -159,7 +143,7 @@ public readonly bool InDonutCone(WPos origin, float innerRadius, float outerRadi return InDonut(origin, innerRadius, outerRadius) && InCone(origin, direction, halfAngle); } - //should work for any non-self intersecting polygon + //should work for any simple (no self intersections or holes) polygon with an IEnumerable/List of points public readonly bool InPolygon(IEnumerable vertices) { float windingNumber = 0; @@ -183,3 +167,23 @@ public readonly bool InPolygon(IEnumerable vertices) return windingNumber!= 0; } } + +public static class Extensions +{ + public static float Cross(this WDir a, WDir b) + { + return a.X * b.Z - a.Z * b.X; + } + + public static float AngleBetween(this WDir v1, WDir v2) + { + _ = v1.Dot(v2); + _ = v1.X * v2.Z - v1.Z * v2.X; + return MathF.Atan2(v1.X * v2.Z - v1.Z * v2.X, v1.Dot(v2)); + } + + public static float Normalized(this float value) + { + return value / MathF.Sqrt(value * value); + } +}