Skip to content

Commit

Permalink
Completed FRU
Browse files Browse the repository at this point in the history
- still need to fix banish1 spots for ai & revise border positions
  • Loading branch information
awgil committed Jan 21, 2025
1 parent 2bbbc6b commit 1072fd0
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 29 deletions.
3 changes: 2 additions & 1 deletion BossMod/Modules/Dawntrail/Ultimate/FRU/FRU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class P3Junction(BossModule module) : Components.CastCounter(module, ActionID.Ma
class P3BlackHalo(BossModule module) : Components.CastSharedTankbuster(module, ActionID.MakeSpell(AID.BlackHalo), new AOEShapeCone(60, 45.Degrees())); // TODO: verify angle
class P4HallowedWingsL(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HallowedWingsL), new AOEShapeRect(80, 20, 0, 90.Degrees()));
class P4HallowedWingsR(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HallowedWingsR), new AOEShapeRect(80, 20, 0, -90.Degrees()));
class P5ParadiseLost(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.ParadiseLostP5AOE));

[ModuleInfo(BossModuleInfo.Maturity.WIP, PrimaryActorOID = (uint)OID.BossP1, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1006, NameID = 9707, PlanLevel = 100)]
[ModuleInfo(BossModuleInfo.Maturity.Verified, PrimaryActorOID = (uint)OID.BossP1, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1006, NameID = 9707, PlanLevel = 100)]
public class FRU(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsCircle(20))
{
private Actor? _bossP2;
Expand Down
4 changes: 3 additions & 1 deletion BossMod/Modules/Dawntrail/Ultimate/FRU/FRUEnums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public enum AID : uint

MemorysEndP4 = 40305, // OracleOfDarknessP4->self, 10.0s cast, range 100 circle, enrage
AbsoluteZeroP4 = 40245, // UsurperOfFrostP4->self, 10.0s cast, range 100 circle, enrage
ParadiseLost = 40263, // Helper->self, no cast, range 100 circle, wipe on p5 failure state
ParadiseLostP4 = 40263, // Helper->self, no cast, range 100 circle, wipe on p5 failure state
IntermissionP5Visual = 40231, // UsurperOfFrostP4->self, no cast, single-target, visual (intermission start)
IntermissionP5Start = 40232, // Helper->self, no cast, range 60 circle, stun + move players to a specific spot

Expand Down Expand Up @@ -286,6 +286,8 @@ public enum AID : uint
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
ParadiseLostP5 = 40327, // BossP5->self, 12.0+9.5s cast, range 100 circle, visual (enrage)
ParadiseLostP5AOE = 40328, // Helper->self, 21.5s cast, range 100 circle, wipe
}

public enum SID : uint
Expand Down
16 changes: 12 additions & 4 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/FRUStates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,8 @@ private void Phase5(uint id)
P5FulgentBlade(id + 0x50000, 6.2f);
P5ParadiseRegained(id + 0x60000, 8.4f);
P5PolarizingStrikes(id + 0x70000, 2.3f);
P5FulgentBlade(id + 0x80000, 8); // TODO: timing...

SimpleState(id + 0xFF0000, 100, "???");
P5FulgentBlade(id + 0x80000, 2.6f);
P5Enrage(id + 0x90000, 8.4f);
}

private void P1CyclonicBreakPowderMarkTrail(uint id, float delay)
Expand Down Expand Up @@ -526,8 +525,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)
ActorCastStart(id + 0x10, _module.BossP4Usurper, AID.Materialization, 5.1f, true)
.ActivateOnEnter<P4AkhRhai>();
ActorCastEnd(id + 0x11, _module.BossP4Usurper, 3, true);
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");
Expand Down Expand Up @@ -718,4 +718,12 @@ private void P5PandorasBox(uint id, float delay)
ActorCast(id, _module.BossP5, AID.PandorasBox, delay, 12, true, "Tank LB")
.SetHint(StateMachine.StateHint.Raidwide);
}

private void P5Enrage(uint id, float delay)
{
ActorCast(id, _module.BossP5, AID.ParadiseLostP5, delay, 12, true);
ComponentCondition<P5ParadiseLost>(id + 2, 9.5f, comp => comp.NumCasts > 0, "Enrage")
.ActivateOnEnter<P5ParadiseLost>()
.DeactivateOnExit<P5ParadiseLost>();
}
}
20 changes: 17 additions & 3 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/P2DiamondDust.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ class P2TwinStillnessSilence(BossModule module) : Components.GenericAOEs(module)
public readonly List<AOEInstance> AOEs = [];
private readonly Actor? _source = module.Enemies(OID.OraclesReflection).FirstOrDefault();
private BitMask _thinIce;
private readonly WPos[] _slideBackPos = new WPos[PartyState.MaxPartySize]; // used for hints only
private P2SinboundHolyVoidzone? _voidzones; // used for hints only

private const float SlideDistance = 32;
Expand All @@ -378,6 +379,15 @@ public void EnableAIHints()

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

public override void Update()
{
if (AOEs.Count != 2)
return;
foreach (var (i, p) in Raid.WithSlot().IncludedInMask(_thinIce))
if (_slideBackPos[i] == default && p.LastFrameMovement != default)
_slideBackPos[i] = p.PrevPosition;
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (_voidzones == null || _source == null)
Expand Down Expand Up @@ -446,7 +456,6 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
{
// if we're behind boss, slide over
zoneList.ForbidInfiniteRect(Module.Center, Angle.FromDirection(sourceOffset), Module.Bounds.Radius);
//zoneList.ForbidCircle(_source.Position, 20);
}
else
{
Expand All @@ -455,12 +464,17 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
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)
// prefer to return to the starting spot, for more natural preposition for next mechanic
if (AOEs.Count == 1 && _slideBackPos[slot] != default && !zoneList.Forbidden.Contains(Angle.FromDirection(_slideBackPos[slot] - actor.Position).Rad))
{
hints.AddForbiddenZone(ShapeDistance.InvertedCircle(_slideBackPos[slot], 1), DateTime.MaxValue);
}
else if (zoneList.Allowed(1.Degrees()).MaxBy(r => (r.max - r.min).Rad) is var best && 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: no good direction can be found, wait for a bit, maybe voidzone will disappear
}
// else: we are already sliding, nothing to do...
}
Expand Down
20 changes: 20 additions & 0 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/P2HallowedRay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

class P2HallowedRay(BossModule module) : Components.GenericWildCharge(module, 3, ActionID.MakeSpell(AID.HallowedRayAOE), 65)
{
public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (Source == null)
return;
if (PlayerRoles[slot] == PlayerRole.Target || Activation > WorldState.FutureTime(2.5f))
{
// stay at the average direction to the raid
// TODO: for melees, allow doing positionals...
WDir averageDir = default;
foreach (var p in Raid.WithoutSlot())
averageDir += (p.Position - Source.Position).Normalized();
hints.AddForbiddenZone(ShapeDistance.InvertedRect(Source.Position, Angle.FromDirection(averageDir), 20, -6, 2), Activation);
}
else
{
// default hints (go to target)
base.AddAIHints(slot, actor, assignment, hints);
}
}

public override void OnEventIcon(Actor actor, uint iconID, ulong targetID)
{
if (iconID == (uint)IconID.HallowedRay)
Expand Down
18 changes: 13 additions & 5 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/P4CrystallizeTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class P4CrystallizeTimeDragonHead(BossModule module) : BossComponent(module)
public readonly List<(Actor head, int side)> Heads = [];
private readonly P4CrystallizeTime? _ct = module.FindComponent<P4CrystallizeTime>();
private readonly List<(Actor puddle, P4CrystallizeTime.Mechanic soaker)> _puddles = [];
private int _numMaelstroms;

public Actor? FindHead(int side) => Heads.FirstOrDefault(v => v.side == side).head;
public static int NumHeadHits(Actor? head) => head == null ? 2 : head.HitboxRadius < 2 ? 1 : 0;
Expand All @@ -114,10 +115,10 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
var pcAssignment = _ct.PlayerMechanics[slot];
foreach (var p in _puddles.Where(p => p.puddle.EventState != 7))
{
if (p.soaker == pcAssignment)
if (p.soaker != pcAssignment)
hints.AddForbiddenZone(ShapeDistance.Circle(p.puddle.Position, 2));
else if (_numMaelstroms >= 6)
hints.GoalZones.Add(hints.GoalProximity(p.puddle.Position, 15, 0.25f));
else
hints.AddForbiddenZone(ShapeDistance.Circle(p.puddle.Position, 1));
}
}
}
Expand Down Expand Up @@ -163,8 +164,15 @@ public override void OnActorCreated(Actor actor)

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID == AID.DrachenWandererDisappear)
Heads.RemoveAll(h => h.head == caster);
switch ((AID)spell.Action.ID)
{
case AID.DrachenWandererDisappear:
Heads.RemoveAll(h => h.head == caster);
break;
case AID.CrystallizeTimeMaelstrom:
++_numMaelstroms;
break;
}
}

private P4CrystallizeTime.Mechanic AssignPuddle(P4CrystallizeTime.Mechanic first, P4CrystallizeTime.Mechanic second) => _puddles.Any(p => p.soaker == first) ? second : first;
Expand Down
8 changes: 1 addition & 7 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/P5FulgentBlade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,9 @@ private void UpdateOrder(WPos firstPos)

private WPos SafeSpot()
{
if (_lines.Count != 6)
if (Lines.Count == 0 || _lines.Count != 6)
return default; // not enough data

if (Lines.Count == 0)
{
// we don't yet know the direction, so just give the approximate safespot (average direction of missing lines)
return Module.Center + 5 * _initialSafespot;
}

return NumCasts switch
{
< 2 => SafeSpot(0, 1, 11), // prepare to dodge into third exaline of the first pair
Expand Down
15 changes: 8 additions & 7 deletions BossMod/Modules/Dawntrail/Ultimate/FRU/P5PolarizingStrikes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,14 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
{
if (_source == null)
return;
if (_aoes.Count == 0 && _baitsActive)

if (_aoes.Count > 0)
{
// avoid aftershock aoe by moving behind boss
base.AddAIHints(slot, actor, assignment, hints);
hints.GoalZones.Add(hints.GoalSingleTarget(_source.Position - 9 * _source.Rotation.ToDirection(), 1, 0.25f));
}
else if (_baitsActive)
{
var role = _config.P5PolarizingStrikesAssignments[assignment];
if (role >= 0)
Expand All @@ -76,12 +83,6 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
hints.AddForbiddenZone(ShapeDistance.InvertedCircle(_source.Position + distance * dir.ToDirection(), 1));
}
}
else
{
// avoid aftershock aoe by moving behind boss
base.AddAIHints(slot, actor, assignment, hints);
hints.GoalZones.Add(hints.GoalSingleTarget(_source.Position - 8 * _source.Rotation.ToDirection(), 1, 0.25f));
}
}

public override void DrawArenaForeground(int pcSlot, Actor pc)
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Util/ArcList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void ForbidInverseCircle(WPos origin, float radius)
var cos = (oo.LengthSq() + Radius * Radius - radius * radius) / (2 * oo.Length() * Radius);
if (cos is <= 1 and >= -1)
{
ForbidArcByLength(center + 180.Degrees(), 180.Degrees() - Angle.Acos(cos));
ForbidArcByLength((center + 180.Degrees()).Normalized(), Angle.Acos(-cos));
}
}

Expand Down

0 comments on commit 1072fd0

Please sign in to comment.