Skip to content

Commit

Permalink
Merge pull request #560 from FFXIV-CombatReborn/mergeWIP
Browse files Browse the repository at this point in the history
merge fru improvements
  • Loading branch information
CarnifexOptimus authored Jan 17, 2025
2 parents 6b18ec5 + d936d55 commit d424906
Show file tree
Hide file tree
Showing 19 changed files with 812 additions and 204 deletions.
12 changes: 12 additions & 0 deletions BossMod/BossModule/AIHints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,5 +330,17 @@ public Func<WPos, float> GoalCombined(Func<WPos, float> singleTarget, Func<WPos,
};
}

// goal zone that returns a value between 0 and weight depending on distance to point; useful for downtime movement targets
public Func<WPos, float> GoalProximity(WPos destination, float maxDistance, float maxWeight)
{
var invDist = 1.0f / maxDistance;
return p =>
{
var dist = (p - destination).Length();
var weight = 1 - Math.Clamp(invDist * dist, 0, 1);
return maxWeight * weight;
};
}

public WPos ClampToBounds(WPos position) => PathfindMapCenter + PathfindMapBounds.ClampToBounds(position - PathfindMapCenter);
}
2 changes: 1 addition & 1 deletion BossMod/Debug/MainDebugWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private unsafe void DrawStatuses()
if (ImGui.Button("Add thin ice"))
{
var player = (Character*)GameObjectManager.Instance()->Objects.IndexSorted[0].Value;
player->GetStatusManager()->SetStatus(20, 911, 20.0f, 320, 0xE0000000, true); // param = distance * 10
player->GetStatusManager()->SetStatus(20, 911, 20.0f, 50, 0xE0000000, true); // param = distance * 10
}

foreach (var elem in ws.Actors)
Expand Down
15 changes: 14 additions & 1 deletion BossMod/Modules/Dawntrail/Ultimate/FRU/FRUConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,20 @@ public class FRUConfig() : ConfigNode()
[GroupPreset("Default", [0, 2, 5, 3, 4, 6, 7, 1])]
public GroupAssignmentUnique P2IntermissionClockSpots = new() { Assignments = [0, 2, 5, 3, 4, 6, 7, 1] };

[PropertyDisplay("P3 Darkest Dance: baiter")]
[PropertyDisplay("P3 Darkest Dance: baiter", tooltip: "Only used by AI")]
[PropertyCombo("MT", "OT")]
public bool P3DarkestDanceOTBait;

[PropertyDisplay("P4 Somber Dance: baiter", tooltip: "Only used by AI")]
[PropertyCombo("MT", "OT")]
public bool P4SomberDanceOTBait = true;

[PropertyDisplay("P5 Akh Morn: side assignments", tooltip: "Only used by AI")]
[GroupDetails(["Left (looking at boss)", "Right (looking at boss)"])]
public GroupAssignmentLightParties P5AkhMornAssignments = GroupAssignmentLightParties.DefaultLightParties();

[PropertyDisplay("P5 Polarizing Strikes: bait order", tooltip: "Only used by AI")]
[GroupDetails(["Left 1", "Left 2", "Left 3", "Left 4", "Right 1", "Right 2", "Right 3", "Right 4"])]
[GroupPreset("TMRH", [0, 4, 3, 7, 1, 5, 2, 6])]
public GroupAssignmentUnique P5PolarizingStrikesAssignments = new() { Assignments = [0, 4, 3, 7, 1, 5, 2, 6] };
}
11 changes: 11 additions & 0 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/FRUEnums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,15 @@ public enum AID : uint
WingsDarkAndLightCleaveDark = 40315, // BossP5->self, no cast, range 100 240?-degree cone on target
WingsDarkAndLightTetherLight = 39879, // Helper->players, no cast, range 4 circle on farthest
WingsDarkAndLightTetherDark = 39880, // Helper->player, no cast, range 4 circle on closest

PolarizingStrikes = 40316, // BossP5->self, 6.5+0.5s cast, single-target, visual (line stacks)
CruelPathOfLightBait = 40317, // Helper->self, 0.5s cast, range 100 width 6 rect (left side)
CruelPathOfDarknessBait = 40318, // Helper->self, 0.5s cast, range 100 width 6 rect (right side)
CruelPathOfLightAOE = 40119, // Helper->self, no cast, range 100 width 6 rect (repeated hit)
CruelPathOfDarknessAOE = 40120, // Helper->self, no cast, range 100 width 6 rect (repeated hit)
PolarizingPaths = 40234, // BossP5->self, 2.5+0.5s cast, single-target, visual (second+ hit)

PandorasBox = 40326, // BossP5->self, 12.0s cast, range 100 circle, raidwide requiring tank LB
}

public enum SID : uint
Expand Down Expand Up @@ -310,6 +319,8 @@ public enum SID : uint
SpellInWaitingDarkAero = 2463, // none->player, extra=0x0
//SpellInWaitingReturn = 4208, // none->player, extra=0x0
//SpellInWaitingReturnII = 4171, // Helper->UsurperOfFrostP4, extra=0x0
LightResistanceDown = 4164, // Helper->player, extra=0x0
DarkResistanceDown = 3323, // Helper->player, extra=0x0
}

public enum IconID : uint
Expand Down
60 changes: 46 additions & 14 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/FRUStates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ private void Phase5(uint id)
P5Start(id, 77);
P5FulgentBlade(id + 0x10000, 5.3f);
P5ParadiseRegained(id + 0x20000, 4.2f);
P5PolarizingStrikes(id + 0x30000, 7.6f);
P5PandorasBox(id + 0x40000, 5.8f);
P5FulgentBlade(id + 0x50000, 6.2f);
P5ParadiseRegained(id + 0x60000, 8.2f); // TODO: timing...
P5PolarizingStrikes(id + 0x70000, 8); // TODO: timing...
P5FulgentBlade(id + 0x80000, 8); // TODO: timing...

SimpleState(id + 0xFF0000, 100, "???");
}
Expand Down Expand Up @@ -524,9 +530,9 @@ private void P4AkhRhai(uint id, float delay)
.ActivateOnEnter<P4Preposition>()
.DeactivateOnExit<P4Preposition>()
.SetHint(StateMachine.StateHint.DowntimeEnd);
ActorCast(id + 0x10, _module.BossP4Usurper, AID.Materialization, 5.1f, 3, true);
ComponentCondition<P4AkhRhai>(id + 0x20, 11.2f, comp => comp.AOEs.Count > 0, "Puddle baits")
ActorCast(id + 0x10, _module.BossP4Usurper, AID.Materialization, 5.1f, 3, true)
.ActivateOnEnter<P4AkhRhai>();
ComponentCondition<P4AkhRhai>(id + 0x20, 11.2f, comp => comp.AOEs.Count > 0, "Puddle baits");
ComponentCondition<P4AkhRhai>(id + 0x30, 2.6f, comp => comp.NumCasts > 0);
ActorTargetable(id + 0x50, _module.BossP4Oracle, true, 3.6f, "Oracle appears");
ComponentCondition<P4AkhRhai>(id + 0x60, 1.6f, comp => comp.NumCasts >= 10 * comp.AOEs.Count, "Puddle resolve")
Expand All @@ -548,22 +554,22 @@ private void P4DarklitDragonsong(uint id, float delay)
ComponentCondition<P4DarklitDragonsongPathOfLight>(id + 0x21, 0.8f, comp => comp.NumCasts > 0, "Proteans")
.DeactivateOnExit<P4DarklitDragonsongPathOfLight>();
ActorCastEnd(id + 0x22, _module.BossP4Oracle, 2.2f, true)
.ActivateOnEnter<DefaultSpiritTaker>();
.ActivateOnEnter<P4DarklitDragonsongSpiritTaker>();
ActorCastStartMulti(id + 0x23, _module.BossP4Usurper, [AID.HallowedWingsL, AID.HallowedWingsR], 0.1f, true);
ComponentCondition<SpiritTaker>(id + 0x24, 0.3f, comp => comp.Spreads.Count == 0, "Jump")
.DeactivateOnExit<SpiritTaker>();
ActorCastStart(id + 0x25, _module.BossP4Oracle, AID.SomberDance, 2.8f)
.ActivateOnEnter<P4HallowedWingsL>()
.ActivateOnEnter<P4HallowedWingsR>()
.ExecOnEnter<P4DarklitDragonsongDarkWater>(comp => comp.ResolveImminent = true);
.ExecOnEnter<P4DarklitDragonsongDarkWater>(comp => comp.Show());
ComponentCondition<P4DarklitDragonsongDarkWater>(id + 0x26, 1.7f, comp => comp.Stacks.Count == 0, "Stacks")
.DeactivateOnExit<P4DarklitDragonsongDarkWater>();
ActorCastEnd(id + 0x27, _module.BossP4Usurper, 0.2f, false, "Side cleave")
.ActivateOnEnter<P4SomberDance>()
.DeactivateOnExit<P4HallowedWingsL>()
.DeactivateOnExit<P4HallowedWingsR>()
.DeactivateOnExit<P4DarklitDragonsong>();
ActorCastEnd(id + 0x28, _module.BossP4Oracle, 3.1f, true);
.DeactivateOnExit<P4HallowedWingsR>();
ActorCastEnd(id + 0x28, _module.BossP4Oracle, 3.1f, true)
.DeactivateOnExit<P4DarklitDragonsong>(); // tethers deactivate ~0.5s before cast end
ComponentCondition<P4SomberDance>(id + 0x29, 0.4f, comp => comp.NumCasts > 0, "Tankbuster 1")
.SetHint(StateMachine.StateHint.Tankbuster);
ComponentCondition<P4SomberDance>(id + 0x2A, 3.2f, comp => comp.NumCasts > 1, "Tankbuster 2")
Expand Down Expand Up @@ -618,8 +624,7 @@ private void P4CrystallizeTime(uint id, float delay)
.ActivateOnEnter<P4CrystallizeTimeTidalLight>();
ComponentCondition<P4CrystallizeTimeMaelstrom>(id + 0x50, 1.1f, comp => comp.NumCasts > 4, "Hourglass 3")
.DeactivateOnExit<P4CrystallizeTimeMaelstrom>()
.DeactivateOnExit<P4CrystallizeTimeHints>()
.ExecOnExit<P4CrystallizeTimeDragonHead>(comp => comp.ShowPuddles = true);
.DeactivateOnExit<P4CrystallizeTimeHints>();
ActorCast(id + 0x60, _module.BossP4Usurper, AID.TidalLight, 2.3f, 3, true, "Exaline NS start")
.ActivateOnEnter<P4CrystallizeTimeRewind>();
ComponentCondition<P4CrystallizeTimeQuietus>(id + 0x70, 4.1f, comp => comp.NumCasts > 0)
Expand All @@ -632,7 +637,7 @@ private void P4CrystallizeTime(uint id, float delay)
.DeactivateOnExit<P4CrystallizeTime>();
ActorCastStart(id + 0x90, _module.BossP4Oracle, AID.SpiritTaker, 0.4f);
ActorCastStart(id + 0x91, _module.BossP4Usurper, AID.CrystallizeTimeHallowedWings1, 2.2f)
.ActivateOnEnter<DefaultSpiritTaker>();
.ActivateOnEnter<P4CrystallizeTimeSpiritTaker>();
ActorCastEnd(id + 0x92, _module.BossP4Oracle, 0.8f);
ComponentCondition<SpiritTaker>(id + 0x93, 0.3f, comp => comp.Spreads.Count == 0, "Jump")
.DeactivateOnExit<SpiritTaker>();
Expand Down Expand Up @@ -683,11 +688,38 @@ private void P5ParadiseRegained(uint id, float delay)
.ActivateOnEnter<P5ParadiseRegainedTowers>(); // first tower appears ~0.9s after cast end, then every 3.5s
ActorCastMulti(id + 0x10, _module.BossP5, [AID.WingsDarkAndLightDL, AID.WingsDarkAndLightLD], 3.2f, 6.9f, true)
.ActivateOnEnter<P5ParadiseRegainedBaits>();
ComponentCondition<P5ParadiseRegainedBaits>(id + 0x20, 0.5f, comp => comp.NumCasts > 0, "Light/dark"); // first tower resolve ~0.1s earlier
ComponentCondition<P5ParadiseRegainedBaits>(id + 0x30, 3.7f, comp => comp.NumCasts > 1, "Dark/light") // second tower resolves ~1s earlier
ComponentCondition<P5ParadiseRegainedBaits>(id + 0x20, 0.3f, comp => comp.NumCasts > 0, "Light/dark"); // first tower resolves at the same time
ComponentCondition<P5ParadiseRegainedBaits>(id + 0x30, 3.7f, comp => comp.NumCasts > 1, "Dark/light") // second tower resolves at the same time
.DeactivateOnExit<P5ParadiseRegainedBaits>();
// note: tethers resolve ~0.7s after cleave, but they won't happen if tether target dies to cleave
ComponentCondition<P5ParadiseRegainedTowers>(id + 0x40, 2.4f, comp => comp.NumCasts > 2, "Towers resolve")
// note: tethers resolve ~0.8s after cleave, but they won't happen if tether target dies to cleave
ComponentCondition<P5ParadiseRegainedTowers>(id + 0x40, 3.4f, comp => comp.NumCasts > 2, "Towers resolve")
.DeactivateOnExit<P5ParadiseRegainedTowers>();
}

private void P5PolarizingStrikes(uint id, float delay)
{
ActorCast(id, _module.BossP5, AID.PolarizingStrikes, delay, 6.5f, true)
.ActivateOnEnter<P5PolarizingStrikes>();
ComponentCondition<P5PolarizingStrikes>(id + 0x10, 0.6f, comp => comp.NumCasts > 0, "Polarizing bait 1");
ActorCastStart(id + 0x20, _module.BossP5, AID.PolarizingPaths, 1.5f, true);
ComponentCondition<P5PolarizingStrikes>(id + 0x21, 0.5f, comp => comp.NumCasts > 2, "Polarizing AOE 1");
ActorCastEnd(id + 0x22, _module.BossP5, 2, true);
ComponentCondition<P5PolarizingStrikes>(id + 0x30, 0.6f, comp => comp.NumCasts > 4, "Polarizing bait 2");
ActorCastStart(id + 0x40, _module.BossP5, AID.PolarizingPaths, 1.5f, true);
ComponentCondition<P5PolarizingStrikes>(id + 0x41, 0.5f, comp => comp.NumCasts > 6, "Polarizing AOE 2");
ActorCastEnd(id + 0x42, _module.BossP5, 2, true);
ComponentCondition<P5PolarizingStrikes>(id + 0x50, 0.6f, comp => comp.NumCasts > 8, "Polarizing bait 3");
ActorCastStart(id + 0x60, _module.BossP5, AID.PolarizingPaths, 1.5f, true);
ComponentCondition<P5PolarizingStrikes>(id + 0x61, 0.5f, comp => comp.NumCasts > 10, "Polarizing AOE 3");
ActorCastEnd(id + 0x62, _module.BossP5, 2, true);
ComponentCondition<P5PolarizingStrikes>(id + 0x70, 0.6f, comp => comp.NumCasts > 12, "Polarizing bait 4");
ComponentCondition<P5PolarizingStrikes>(id + 0x80, 2.0f, comp => comp.NumCasts > 14, "Polarizing AOE 4")
.DeactivateOnExit<P5PolarizingStrikes>();
}

private void P5PandorasBox(uint id, float delay)
{
ActorCast(id, _module.BossP5, AID.PandorasBox, delay, 12, true, "Tank LB")
.SetHint(StateMachine.StateHint.Raidwide);
}
}
12 changes: 11 additions & 1 deletion BossMod/Modules/Dawntrail/Ultimate/FRU/P2AbsoluteZero.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,26 @@ public override IEnumerable<Source> Sources(int slot, Actor actor)

class P2HiemalStorm(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.HiemalStormAOE), 7)
{
private bool _slowDodges;

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
// storms are cast every 3s, ray voidzones appear every 2s; to place voidzones more tightly, we pretend radius is smaller during first half of cast
var deadline = WorldState.FutureTime(1.5f);
// there's no point doing it before first voidzone appears, however
var deadline = _slowDodges ? WorldState.FutureTime(1.5f) : DateTime.MaxValue;
foreach (var c in Casters)
{
var activation = c.Activation;
hints.AddForbiddenZone(ShapeDistance.Circle(c.Origin, activation > deadline ? 4 : 7), activation);
}
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
base.OnEventCast(caster, spell);
if ((AID)spell.Action.ID == AID.HiemalRay)
_slowDodges = true;
}
}

class P2HiemalRay(BossModule module) : Components.PersistentVoidzoneAtCastTarget(module, 4, ActionID.MakeSpell(AID.HiemalRay), module => module.Enemies(OID.HiemalRayVoidzone).Where(z => z.EventState != 7), 0.7f);
Expand Down
68 changes: 48 additions & 20 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/P2DiamondDust.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ class P2TwinStillnessSilence(BossModule module) : Components.GenericAOEs(module)
private readonly Actor? _source = module.Enemies(OID.OraclesReflection).FirstOrDefault();
private BitMask _thinIce;
private P2SinboundHolyVoidzone? _voidzones; // used for hints only
private const float SlideDistance = 32;

private readonly AOEShapeCone _shapeFront = new(30, 135.Degrees());
private readonly AOEShapeCone _shapeBack = new(30, 45.Degrees());
Expand Down Expand Up @@ -412,29 +413,56 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
hints.AddForbiddenZone(ShapeDistance.Circle(Arena.Center, 16), WorldState.FutureTime(50));
hints.AddForbiddenZone(ShapeDistance.InvertedCone(Arena.Center, 100, desiredDir, halfWidth), DateTime.MaxValue);
}
return;
}

if (AOEs.Count == 0)
{
// if we're behind boss, slide over
hints.AddForbiddenZone(ShapeDistance.Rect(_source.Position, _source.Rotation, 20, 20, 20), DateTime.MaxValue);
}
else
else if (actor.LastFrameMovement == default)
{
// otherwise just dodge next aoe
ref var nextAOE = ref AOEs.Ref(0);
hints.AddForbiddenZone(nextAOE.Shape.Distance(nextAOE.Origin, nextAOE.Rotation), nextAOE.Activation);
}
// at this point, we have thin ice, so we can either stay or move fixed distance
var sourceOffset = _source.Position - Arena.Center;
var needToMove = AOEs.Count > 0 ? AOEs[0].Check(actor.Position) : NumCasts == 0 && sourceOffset.Dot(actor.Position - Arena.Center) > 0;
if (!needToMove)
return;

// ensure we don't slide over voidzones
foreach (var z in _voidzones.Sources(Module))
{
var offset = z.Position - actor.Position;
var dist = offset.Length();
if (dist > 6)
hints.AddForbiddenZone(ShapeDistance.Cone(actor.Position, 100, Angle.FromDirection(offset), Angle.Asin(dist / 6)));
var zoneList = new ArcList(actor.Position, SlideDistance);
zoneList.ForbidInverseCircle(Arena.Center, Arena.Bounds.Radius);

foreach (var z in _voidzones.Sources(Module))
{
var offset = z.Position - actor.Position;
var dist = offset.Length();
if (dist >= SlideDistance)
{
// voidzone center is outside slide distance => forbid voidzone itself
zoneList.ForbidCircle(z.Position, 6);
}
else if (dist >= 6)
{
// forbid the voidzone's shadow
zoneList.ForbidArcByLength(Angle.FromDirection(offset), Angle.Asin(6 / dist));
}
// else: we're already in voidzone, oh well
}

if (AOEs.Count == 0)
{
// if we're behind boss, slide over
zoneList.ForbidInfiniteRect(Arena.Center, Angle.FromDirection(sourceOffset), Arena.Bounds.Radius);
//zoneList.ForbidCircle(_source.Position, 20);
}
else
{
// dodge next aoe
ref var nextAOE = ref AOEs.Ref(0);
zoneList.ForbidInfiniteCone(nextAOE.Origin, nextAOE.Rotation, ((AOEShapeCone)nextAOE.Shape).HalfAngle);
}

var best = zoneList.Allowed(1.Degrees()).MaxBy(r => (r.max - r.min).Rad);
if (best.max.Rad > best.min.Rad)
{
var dir = 0.5f * (best.min + best.max);
hints.AddForbiddenZone(ShapeDistance.InvertedCircle(actor.Position + SlideDistance * dir.ToDirection(), 1), DateTime.MaxValue);
}
}
// else: we are already sliding, nothing to do...
}

public override void DrawArenaForeground(int pcSlot, Actor pc)
Expand Down Expand Up @@ -474,7 +502,7 @@ public override void OnStatusGain(Actor actor, ActorStatus status)
}
}

class P2ThinIce(BossModule module) : Components.ThinIce(module, 32, true)
class P2ThinIce(BossModule module) : Components.ThinIce(module, 32)
{
public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent<P2TwinStillnessSilence>()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) ||
!Module.InBounds(pos);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
foreach (var (i, p) in Raid.WithSlot(false, true, true).Exclude(slot))
{
var avoidRadius = avoidBlizzard && States[i].HaveDarkBlizzard ? 12 : 8;
hints.AddForbiddenZone(ShapeDistance.Circle(p.Position, avoidRadius + 1));
hints.AddForbiddenZone(ShapeDistance.Circle(p.Position, avoidRadius));
}
var lasers = Module.FindComponent<P3UltimateRelativitySinboundMeltdownAOE>();
if (lasers != null)
Expand Down Expand Up @@ -401,7 +401,7 @@ class P3UltimateRelativityDarkBlizzard(BossModule module) : Components.GenericAO
private readonly List<Actor> _sources = [];
private DateTime _activation;

private static readonly AOEShapeDonut _shape = new(4, 12); // TODO: verify inner radius
private static readonly AOEShapeDonut _shape = new(3, 12); // TODO: verify inner radius

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => _sources.Select(s => new AOEInstance(_shape, s.Position, default, _activation));

Expand Down
13 changes: 13 additions & 0 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/P4AkhRhai.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ class P4AkhRhai(BossModule module) : Components.GenericAOEs(module, ActionID.Mak

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => AOEs;

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
base.AddAIHints(slot, actor, assignment, hints);
if (AOEs.Count == 0)
{
// preposition for baits - note that this is very arbitrary...
var off = 10 * 45.Degrees().ToDirection();
var p1 = ShapeDistance.Circle(Arena.Center + off, 1);
var p2 = ShapeDistance.Circle(Arena.Center - off, 1);
hints.AddForbiddenZone(p => -Math.Min(p1(p), p2(p)), DateTime.MaxValue);
}
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.AkhRhai)
Expand Down
Loading

0 comments on commit d424906

Please sign in to comment.