Skip to content

Commit

Permalink
Merge pull request #499 from FFXIV-CombatReborn/mergeWIP
Browse files Browse the repository at this point in the history
refactored Slice is Right module, Jeuno boss 4 knockback AI
  • Loading branch information
CarnifexOptimus authored Dec 13, 2024
2 parents 29b6dc2 + 67246a3 commit 8982223
Show file tree
Hide file tree
Showing 16 changed files with 267 additions and 187 deletions.
2 changes: 0 additions & 2 deletions BossMod/Autorotation/akechi/AkechiGNB.cs
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,6 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa
if (ShouldUseDoubleDown(ddStrat, primaryTarget))
QueueGCD(AID.DoubleDown, primaryTarget, ddStrat == OffensiveStrategy.Force || Ammo == 1 ? GCDPriority.ForcedGCD : GCDPriority.DoubleDown);


//Gnashing Fang Combo execution
if (GunComboStep == 1)
QueueGCD(AID.SavageClaw, primaryTarget, gfStrat == GnashingStrategy.ForceClaw ? GCDPriority.ForcedGCD : GCDPriority.GF23);
Expand All @@ -543,7 +542,6 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa
if (Unlocked(AID.BurstStrike) && Unlocked(AID.Bloodfest) && ShouldUseBurstStrike(strikeStrat, primaryTarget))
QueueGCD(AID.BurstStrike, primaryTarget, strikeStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : nmCD < 1 ? GCDPriority.ForcedGCD : GCDPriority.BurstStrike);


//Fated Circle execution
var fcStrat = strategy.Option(Track.FatedCircle).As<OffensiveStrategy>();
if (ShouldUseFatedCircle(fcStrat, primaryTarget))
Expand Down
1 change: 1 addition & 0 deletions BossMod/Data/Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public sealed record class ActorCastInfo
public float RemainingTime => TotalTime - ElapsedTime;
public float NPCTotalTime => TotalTime + NPCFinishDelay;
public float NPCRemainingTime => NPCTotalTime - ElapsedTime;
public float AdjustedTotalTime => TotalTime + Action.CastTimeExtra();

public bool IsSpell() => Action.Type == ActionType.Spell;
public bool IsSpell<AID>(AID aid) where AID : Enum => Action == ActionID.MakeSpell(aid);
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Data/ActorState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void Tick(float dt)
{
act.PrevPosRot = act.PosRot;
if (act.CastInfo != null)
act.CastInfo.ElapsedTime += dt;
act.CastInfo.ElapsedTime = Math.Min(act.CastInfo.ElapsedTime + dt, act.CastInfo.AdjustedTotalTime);
}
}

Expand Down
12 changes: 6 additions & 6 deletions BossMod/Modules/Dawntrail/Alliance/A11Prishe/AuroralUppercut.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace BossMod.Dawntrail.Alliance.A11Prishe;
class AuroralUppercut(BossModule module) : Components.Knockback(module, ignoreImmunes: true)
{
private Source? _source;
public override IEnumerable<Source> Sources(int slot, Actor actor) => Utils.ZeroOrOne(_source);
public override IEnumerable<Source> Sources(int slot, Actor actor) => _source != null && actor.FindStatus(SID.Knockback) == null ? Utils.ZeroOrOne(_source) : [];

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
Expand All @@ -30,15 +30,15 @@ public override void OnStatusLose(Actor actor, ActorStatus status)

class AuroralUppercutHint(BossModule module) : Components.GenericAOEs(module)
{
private static readonly Angle a45 = 45.Degrees(), a135 = 135.Degrees(), a44 = 44.Degrees(), a10 = 10.Degrees(), a59 = 59.Degrees();
private static readonly Angle a45 = 45.Degrees(), a135 = 135.Degrees(), a44 = 44.Degrees(), a13 = 12.5f.Degrees(), a59 = 59.Degrees();
private static readonly WPos center = A11Prishe.ArenaCenter;
private AOEInstance? _aoe;
private static readonly AOEShapeCustom hintENVC00020001KB25 = new([new DonutSegmentHA(center, 4, 10, -144.Degrees(), a44), new DonutSegmentHA(center, 4, 10, 36.Degrees(), a44)],
[new ConeHA(center, 10, -a135, a10), new ConeHA(center, 10, a45, a10)], InvertForbiddenZone: true);
[new ConeHA(center, 10, -a135, a13), new ConeHA(center, 10, a45, a13)], InvertForbiddenZone: true);
private static readonly AOEShapeCustom hintENVC02000100KB25 = new([new DonutSegmentHA(center, 4, 10, 126.Degrees(), a44), new DonutSegmentHA(center, 4, 10, -54.Degrees(), a44)],
[new ConeHA(center, 10, a135, a10), new ConeHA(center, 10, -a45, a10)], InvertForbiddenZone: true);
private static readonly AOEShapeCustom hintENVC00020001KB38 = new([new ConeHA(center, 5, -a135, a10), new ConeHA(center, 5, a45, a10)], InvertForbiddenZone: true);
private static readonly AOEShapeCustom hintENVC02000100KB38 = new([new ConeHA(center, 5, a135, a10), new ConeHA(center, 5, -a45, a10)], InvertForbiddenZone: true);
[new ConeHA(center, 10, a135, a13), new ConeHA(center, 10, -a45, a13)], InvertForbiddenZone: true);
private static readonly AOEShapeCustom hintENVC00020001KB38 = new([new ConeHA(center, 5, -a135, a13), new ConeHA(center, 5, a45, a13)], InvertForbiddenZone: true);
private static readonly AOEShapeCustom hintENVC02000100KB38 = new([new ConeHA(center, 5, a135, a13), new ConeHA(center, 5, -a45, a13)], InvertForbiddenZone: true);
private static readonly AOEShapeCustom hintENVC00020001KB12 = new([new ConeHA(center, 5, a135, a59), new ConeHA(center, 5, -a45, a59),
new ConeV(ArenaChanges.MiddleENVC00020001[0].Center + new WDir(-9, -9), 3, -a135, a45, 3),
new ConeV(ArenaChanges.MiddleENVC00020001[1].Center + new WDir(9, 9), 3, a45, a45, 3),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ public class A14ShadowLord(WorldState ws, Actor primary) : BossModule(ws, primar
private const float HalfWidth = 1.9f;
public static readonly WPos ArenaCenter = new(150, 800);
public static readonly ArenaBoundsCircle DefaultBounds = new(30);
private static readonly Circle[] circles = [new(new(166, 800), RadiusSmall), new(new(134, 800), RadiusSmall),
public static readonly Circle[] Circles = [new(new(166, 800), RadiusSmall), new(new(134, 800), RadiusSmall),
new(new(150, 816), RadiusSmall), new(new(150, 784), RadiusSmall)];
private static readonly RectangleSE[] rects = [new(circles[1].Center, circles[2].Center, HalfWidth), new(circles[1].Center, circles[3].Center, HalfWidth),
new(circles[3].Center, circles[0].Center, HalfWidth), new(circles[0].Center, circles[2].Center, HalfWidth)];
public static readonly Shape[] Combined = [.. circles, .. rects];
private static readonly RectangleSE[] rects = [new(Circles[1].Center, Circles[2].Center, HalfWidth), new(Circles[1].Center, Circles[3].Center, HalfWidth),
new(Circles[3].Center, Circles[0].Center, HalfWidth), new(Circles[0].Center, Circles[2].Center, HalfWidth)];
public static readonly Shape[] Combined = [.. Circles, .. rects];
public static readonly ArenaBoundsComplex ComplexBounds = new(Combined);
}
30 changes: 0 additions & 30 deletions BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/CthonicFury.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,36 +64,6 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell)
};
}

class DarkNebula(BossModule module) : Components.Knockback(module)
{
public readonly List<Actor> Casters = [];

public override IEnumerable<Source> Sources(int slot, Actor actor)
{
foreach (var caster in Casters.Take(2))
{
var dir = caster.CastInfo?.Rotation ?? caster.Rotation;
var kind = dir.ToDirection().OrthoL().Dot(actor.Position - caster.Position) > 0 ? Kind.DirLeft : Kind.DirRight;
yield return new(caster.Position, 20, Module.CastFinishAt(caster.CastInfo), null, dir, kind);
}
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.DarkNebulaShort or AID.DarkNebulaLong)
Casters.Add(caster);
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.DarkNebulaShort or AID.DarkNebulaLong)
{
++NumCasts;
Casters.Remove(caster);
}
}
}

class EchoesOfAgony(BossModule module) : Components.StackWithIcon(module, (uint)IconID.EchoesOfAgony, ActionID.MakeSpell(AID.EchoesOfAgonyAOE), 5, 9.2f, PartyState.MaxAllianceSize, PartyState.MaxAllianceSize)
{
public override void OnCastStarted(Actor caster, ActorCastInfo spell)
Expand Down
81 changes: 81 additions & 0 deletions BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/DarkNebula.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
namespace BossMod.Dawntrail.Alliance.A14ShadowLord;

class DarkNebula(BossModule module) : Components.Knockback(module)
{
private const int Length = 4;
private const float HalfWidth = 1.75f;

public readonly List<Actor> Casters = [];

private static readonly Angle a90 = 90.Degrees();
private static readonly List<(Predicate<WPos> Matcher, int[] CircleIndices, WDir Directions)> PositionMatchers =
[
(pos => pos == new WPos(142, 792), [3, 1], 45.Degrees().ToDirection()), // 135°
(pos => pos == new WPos(158, 792), [0, 3], -135.Degrees().ToDirection()), // 45°
(pos => pos == new WPos(158, 808), [2, 0], -45.Degrees().ToDirection()), // -45°
(pos => pos.AlmostEqual(new WPos(142, 808), 1), [1, 2], 135.Degrees().ToDirection()) // -135°
];

public override IEnumerable<Source> Sources(int slot, Actor actor)
{
var count = Casters.Count;
if (count != 0)
{
for (var i = 0; i < count; ++i)
{
if (i < 2)
{
var caster = Casters[i];
var dir = caster.CastInfo?.Rotation ?? caster.Rotation;
var kind = dir.ToDirection().OrthoL().Dot(actor.Position - caster.Position) > 0 ? Kind.DirLeft : Kind.DirRight;
yield return new(caster.Position, 20, Module.CastFinishAt(caster.CastInfo), null, dir, kind);
}
}
}
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.DarkNebulaShort or AID.DarkNebulaLong)
Casters.Add(caster);
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.DarkNebulaShort or AID.DarkNebulaLong)
{
++NumCasts;
Casters.Remove(caster);
}
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (Casters.Count == 0)
return;

var forbidden = new List<Func<WPos, float>>();
var caster0 = Casters[0];
static Func<WPos, float> CreateForbiddenZone(int circleIndex, WDir dir)
=> ShapeDistance.InvertedRect(A14ShadowLord.Circles[circleIndex].Center, dir, Length, 0, HalfWidth);

var mapping = PositionMatchers.FirstOrDefault(m => m.Matcher(caster0.Position));

if (Casters.Count == 1)
{
foreach (var circleIndex in mapping.CircleIndices)
{
forbidden.Add(CreateForbiddenZone(circleIndex, mapping.Directions));
}
}
else
{
var caster1 = Casters[1];
var rotationMatch = caster0.Rotation.AlmostEqual(caster1.Rotation + a90, Angle.DegToRad);
var circleIndex = rotationMatch ? mapping.CircleIndices.First() : mapping.CircleIndices.Last();
forbidden.Add(CreateForbiddenZone(circleIndex, mapping.Directions));
}

hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), Sources(slot, actor).FirstOrDefault().Activation);
}
}
15 changes: 9 additions & 6 deletions BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/GigaSlash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,26 @@ class GigaSlash(BossModule module) : Components.GenericAOEs(module)
AID.GigaSlashNightfallBAOE3, AID.GigaSlashNightfallLAOE1, AID.GigaSlashNightfallRAOE2, AID.GigaSlashNightfallRAOE1, AID.GigaSlashNightfallLAOE2];
private static readonly AOEShapeCone[] _shapes = [new(60, 112.5f.Degrees()), new(60, 135.Degrees()), new(60, 105.Degrees())];

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => AOEs.Take(1);
public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
if (AOEs.Count > 0)
yield return AOEs[0] with { Risky = Module.FindComponent<DarkNebula>()?.Casters.Count == 0 };
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (AOEs.Count == 0)
return;
base.AddAIHints(slot, actor, assignment, hints);
// stay close to the middle if there is next imminent aoe from same origin
if (AOEs.Count > 1 && AOEs[0].Origin == AOEs[1].Origin)
if (Module.FindComponent<DarkNebula>()?.Casters.Count == 0 && AOEs.Count > 1 && AOEs[0].Origin == AOEs[1].Origin)
hints.AddForbiddenZone(ShapeDistance.InvertedCircle(AOEs[0].Origin, 3), AOEs[0].Activation);
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
var rotation = spell.Rotation;
var position = caster.Position;

void AddAOE(AOEShapeCone shape, float rotationOffset, float finishOffset)
=> AOEs.Add(new(shape, position, rotation + rotationOffset.Degrees(), Module.CastFinishAt(spell, finishOffset)));
=> AOEs.Add(new(shape, caster.Position, spell.Rotation + rotationOffset.Degrees(), Module.CastFinishAt(spell, finishOffset)));

switch ((AID)spell.Action.ID)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
for (var i = 0; i < len; ++i)
{
var o = sph[i];
orbs.Add(ShapeDistance.InvertedCircle(o.Position + 0.5f * o.Rotation.ToDirection(), 0.5f));
orbs.Add(ShapeDistance.InvertedCircle(o.Position + 0.5f * o.Rotation.ToDirection(), 0.55f));
}
}
if (orbs.Count != 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
var orbs = new List<Func<WPos, float>>();
if (ActiveOrbs.Any())
foreach (var o in ActiveOrbs)
orbs.Add(ShapeDistance.InvertedCircle(o.Position + 0.5f * o.Rotation.ToDirection(), 0.75f));
orbs.Add(ShapeDistance.InvertedCircle(o.Position + 0.55f * o.Rotation.ToDirection(), 0.75f));
if (orbs.Count > 0)
hints.AddForbiddenZone(p => orbs.Max(f => f(p)));
}
Expand Down
13 changes: 9 additions & 4 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/FRUAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa
{
MovementStrategy.Prepull => PrepullPosition(module, assignment),
MovementStrategy.DragToCenter => DragToCenterPosition(module),
MovementStrategy.MaxMeleeNearest => primaryTarget != null ? ClosestInMelee(Player.Position, primaryTarget) : Player.Position,
MovementStrategy.MaxMeleeNearest => primaryTarget != null ? primaryTarget.Position + 7.5f * (Player.Position - primaryTarget.Position).Normalized() : Player.Position,
MovementStrategy.ClockSpot => ClockSpotPosition(module, assignment, 6),
_ => Player.Position
};
Expand All @@ -45,13 +45,18 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa
_ => module.PrimaryActor.Position + new WDir(0, 12.5f)
};

// notes on boss movement: boss top speed is ~8.5m, it moves up to distance 7.5m (both hitboxes + 2m)
// empyrically, if i stand still, i can start moving when boss is ~11m away and it will still be dragged to intended spot
private WPos DragToCenterPosition(FRU module)
{
if (module.PrimaryActor.Position.Z >= module.Center.Z - 1)
return module.Center - new WDir(0, 6); // boss is positioned, go to N clockspot
var dragSpot = module.Center + new WDir(0, 7.75f); // we need to stay approx here, it's fine to overshoot a little bit - then when boss teleports, it won't turn
var meleeSpot = ClosestInMelee(dragSpot, module.PrimaryActor);
return UptimeDowntimePos(dragSpot, meleeSpot, 0, GCD);
var dragDistance = module.PrimaryActor.HitboxRadius + Player.HitboxRadius + 2.25f; // we need to stay approx here, it's fine to overshoot a little bit - then when boss teleports, it won't turn
var meleeDistance = module.PrimaryActor.HitboxRadius + Player.HitboxRadius + 2.75f; // -0.25 is a small extra leeway
var dragDir = (module.Center - module.PrimaryActor.Position).Normalized();
var dragSpot = module.Center + dragDistance * dragDir;
var timeToMelee = ((dragSpot - module.PrimaryActor.Position).Length() - meleeDistance) / (Speed() + 8.5f); // assume 8.5 boss speed...
return GCD > timeToMelee + 0.1f ? dragSpot : module.PrimaryActor.Position + meleeDistance * dragDir;
}

private WPos ClockSpotPosition(FRU module, PartyRolesConfig.Assignment assignment, float range) => assignment switch
Expand Down
Loading

0 comments on commit 8982223

Please sign in to comment.