Skip to content

Commit

Permalink
Merge pull request #50 from CarnifexOptimus/slave_lotsofchanges
Browse files Browse the repository at this point in the history
ArenaBoundsDonut, ArenaBoundsUnion, ArenaBoundsPolygon background fix, other small improvements
  • Loading branch information
CarnifexOptimus authored Apr 20, 2024
2 parents 9488654 + 9c95c82 commit c3acf45
Show file tree
Hide file tree
Showing 43 changed files with 582 additions and 183 deletions.
3 changes: 1 addition & 2 deletions BossMod/BossModule/AOEShapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
302 changes: 293 additions & 9 deletions BossMod/BossModule/ArenaBounds.cs

Large diffs are not rendered by default.

81 changes: 67 additions & 14 deletions BossMod/BossModule/MiniArena.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
{
Expand All @@ -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);

}
}

Expand Down Expand Up @@ -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<WPos> 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<IEnumerable<WPos>> GroupPolygons(IEnumerable<WPos> vertices)
{
List<WPos> 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;
Expand Down
59 changes: 53 additions & 6 deletions BossMod/Components/Knockback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SafeWall>? safeWalls = null) : CastCounter(module, aid)
{
public enum Kind
{
Expand All @@ -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
Expand All @@ -34,6 +45,7 @@ protected struct PlayerImmuneState
public readonly bool ImmuneAt(DateTime time) => RoleBuffExpire > time || JobBuffExpire > time || DutyBuffExpire > time;
}

public IEnumerable<SafeWall> 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
Expand Down Expand Up @@ -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;
Expand All @@ -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<SafeWall>? safeWalls = null)
: Knockback(module, aid, ignoreImmunes, maxCasts, stopAtWall, stopAfterWall, safeWalls)
{
public float Distance = distance;
public AOEShape? Shape = shape;
Expand Down
6 changes: 3 additions & 3 deletions BossMod/Components/LineOfSightAOE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WPos, float> invertedDistanceToSafe = p =>
float invertedDistanceToSafe(WPos p)
{
var off = p - Origin.Value;
var distOrigin = off.Length();
Expand All @@ -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)
Expand All @@ -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<Actor> _casters = new();
private readonly List<Actor> _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)
Expand Down
25 changes: 24 additions & 1 deletion BossMod/Modules/DemoModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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<Source> 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<DemoComponent>();
// ActivateComponent<RayIntersectionTest>();
}

}
13 changes: 7 additions & 6 deletions BossMod/Modules/Endwalker/Alliance/A12Rhalgr/A12Rhalgr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WPos> 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<WPos> 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)];
}

8 changes: 2 additions & 6 deletions BossMod/Modules/Endwalker/Alliance/A12Rhalgr/RhalgrBeacon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SafeWall> 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))];
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
Loading

0 comments on commit c3acf45

Please sign in to comment.