Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/awgil/ffxiv_bossmod into wip
Browse files Browse the repository at this point in the history
  • Loading branch information
xanunderscore committed Dec 29, 2024
2 parents 5f7182a + c0148ee commit 18dff0e
Show file tree
Hide file tree
Showing 34 changed files with 1,464 additions and 33 deletions.
7 changes: 5 additions & 2 deletions BossMod/AI/AIRotationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ protected bool InMeleeRange(Actor target, WPos position)

protected void SetForcedMovement(WPos? pos, float tolerance = 0.1f)
{
var dir = (pos ?? Player.Position) - Player.Position;
Hints.ForcedMovement = dir.LengthSq() > tolerance * tolerance ? dir.ToVec3(Player.PosRot.Y) : default;
if (pos != null)
{
var dir = pos.Value - Player.Position;
Hints.ForcedMovement = dir.LengthSq() > tolerance * tolerance ? dir.ToVec3(Player.PosRot.Y) : default;
}
}

protected WPos ClosestInRange(WPos pos, WPos target, float maxRange)
Expand Down
4 changes: 4 additions & 0 deletions BossMod/Debug/DebugAutorotation.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BossMod.Autorotation;
using ImGuiNET;

namespace BossMod;

Expand All @@ -12,5 +13,8 @@ public void Draw()
if (player == null)
return;
new AIHintsVisualizer(autorot.Hints, autorot.Bossmods.WorldState, player, 3).Draw(_tree);

if (ImGui.Button("Gaze!"))
autorot.Hints.ForbiddenDirections.Add((player.Rotation, 45.Degrees(), default));
}
}
13 changes: 8 additions & 5 deletions BossMod/Debug/DebugInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ internal sealed unsafe class DebugInput : IDisposable
private readonly AIHints _hints;
private readonly MovementOverride _move;
//private readonly AI.AIController _navi;
private Vector3 _prevPos;
private Vector4 _prevPosRot;
private float _prevSpeed;
private bool _wannaMove;
private float _moveDir;
Expand Down Expand Up @@ -143,13 +143,16 @@ public void Draw()
var dt = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->FrameDeltaTime;

var player = _ws.Party.Player();
var curPos = player?.PosRot.XYZ() ?? new();
var speed = (curPos - _prevPos) / dt;
var curPosRot = player?.PosRot ?? new();
var speed = (curPosRot.XYZ() - _prevPosRot.XYZ()) / dt;
var speedAbs = speed.Length();
var accel = (speedAbs - _prevSpeed) / dt;
_prevPos = curPos;
var rotSpeed = (curPosRot.W - _prevPosRot.W).Radians().Normalized() / dt;
//if (curPosRot.W != _prevPosRot.W)
// Service.Log($"ROT: {_prevPosRot.W.Radians()} -> {curPosRot.W.Radians()} over {dt} (s={rotSpeed})");
_prevPosRot = curPosRot;
_prevSpeed = speedAbs;
ImGui.TextUnformatted($"Speed={speedAbs:f3}, SpeedH={speed.XZ().Length():f3}, SpeedV={speed.Y:f3}, Accel={accel:f3}, Azimuth={Angle.FromDirection(new(speed.XZ()))}, Altitude={Angle.FromDirection(new(speed.Y, speed.XZ().Length()))}");
ImGui.TextUnformatted($"Speed={speedAbs:f3}, SpeedH={speed.XZ().Length():f3}, SpeedV={speed.Y:f3}, RSpeed={rotSpeed}, Accel={accel:f3}, Azimuth={Angle.FromDirection(new(speed.XZ()))}, Altitude={Angle.FromDirection(new(speed.Y, speed.XZ().Length()))}");
ImGui.TextUnformatted($"MO: desired={_move.DesiredDirection}, user={_move.UserMove}, actual={_move.ActualMove}");
//Service.Log($"Speed: {speedAbs:f3}, accel: {accel:f3}");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace BossMod.Dawntrail.Chaotic.Ch01CloudOfDarkness;

class ActivePivotParticleBeam(BossModule module) : Components.GenericRotatingAOE(module)
{
private static readonly AOEShapeRect _shape = new(40, 9, 40);

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
var rotation = (AID)spell.Action.ID switch
{
AID.ActivePivotParticleBeamCW => -22.5f.Degrees(),
AID.ActivePivotParticleBeamCCW => 22.5f.Degrees(),
_ => default
};
if (rotation != default)
Sequences.Add(new(_shape, caster.Position, spell.Rotation, rotation, Module.CastFinishAt(spell, 0.6f), 1.6f, 5));
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID == AID.ActivePivotParticleBeamAOE && Sequences.Count > 0)
AdvanceSequence(0, WorldState.CurrentTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace BossMod.Dawntrail.Chaotic.Ch01CloudOfDarkness;

class BladeOfDarkness(BossModule module) : Components.GenericAOEs(module)
{
private AOEInstance? _aoe;

private static readonly AOEShapeDonutSector _shapeIn = new(12, 60, 75.Degrees());
private static readonly AOEShapeCone _shapeOut = new(30, 90.Degrees());

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe);

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
AOEShape? shape = (AID)spell.Action.ID switch
{
AID.BladeOfDarknessLAOE or AID.BladeOfDarknessRAOE => _shapeIn,
AID.BladeOfDarknessCAOE => _shapeOut,
_ => null
};
if (shape != null)
_aoe = new(shape, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell));
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.BladeOfDarknessLAOE or AID.BladeOfDarknessRAOE or AID.BladeOfDarknessCAOE)
_aoe = null;
}
}
20 changes: 20 additions & 0 deletions BossMod/Modules/Dawntrail/Chaotic/Ch01CloudOfDarkness/Break.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace BossMod.Dawntrail.Chaotic.Ch01CloudOfDarkness;

class Break(BossModule module) : Components.GenericGaze(module)
{
private readonly List<Eye> _eyes = [];

public override IEnumerable<Eye> ActiveEyes(int slot, Actor actor) => _eyes;//_casters.Where(c => c.CastInfo?.TargetID != actor.InstanceID).Select(c => new Eye(EyePosition(c), Module.CastFinishAt(c.CastInfo), Range: range));

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.BreakBoss or AID.BreakEye)
_eyes.Add(new(caster.Position, Module.CastFinishAt(spell, 0.9f)));
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.BreakBossAOE or AID.BreakEyeAOE)
_eyes.RemoveAll(eye => eye.Position.AlmostEqual(caster.Position, 1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
namespace BossMod.Dawntrail.Chaotic.Ch01CloudOfDarkness;

class DelugeOfDarkness1(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.DelugeOfDarkness1));
class Flare(BossModule module) : Components.BaitAwayIcon(module, new AOEShapeCircle(25), (uint)IconID.Flare, ActionID.MakeSpell(AID.FlareAOE), 8.1f, true);
class FloodOfDarkness(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.FloodOfDarkness));
class DelugeOfDarkness2(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.DelugeOfDarkness2));
class StygianShadow(BossModule module) : Components.Adds(module, (uint)OID.StygianShadow);
class Atomos(BossModule module) : Components.Adds(module, (uint)OID.Atomos);
class DarkDominion(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.DarkDominion));
class GhastlyGloomCross(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.GhastlyGloomCross), new AOEShapeCross(40, 15));
class GhastlyGloomDonut(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.GhastlyGloomDonut), new AOEShapeDonut(21, 40));
class FloodOfDarknessAdd(BossModule module) : Components.CastInterruptHint(module, ActionID.MakeSpell(AID.FloodOfDarknessAdd)); // TODO: only if add is player's?..
class Excruciate(BossModule module) : Components.BaitAwayCast(module, ActionID.MakeSpell(AID.Excruciate), new AOEShapeCircle(4), true);
class LoomingChaos(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.LoomingChaosAOE), "Raidwide + swap positions");

class Ch01CloudOfDarknessStates : StateMachineBuilder
{
public Ch01CloudOfDarknessStates(BossModule module) : base(module)
{
DeathPhase(0, SinglePhase);
}

private void SinglePhase(uint id)
{
SimpleState(id + 0xFF0000, 10000, "???")
.ActivateOnEnter<BladeOfDarkness>()
.ActivateOnEnter<DelugeOfDarkness1>()
.ActivateOnEnter<GrimEmbraceBait>()
.ActivateOnEnter<GrimEmbraceAOE>()
.ActivateOnEnter<RazingVolleyParticleBeam>()
.ActivateOnEnter<RapidSequenceParticleBeam>()
.ActivateOnEnter<EndeathVortex>()
.ActivateOnEnter<EndeathAOE>()
.ActivateOnEnter<EnaeroKnockback>()
.ActivateOnEnter<EnaeroAOE>()
.ActivateOnEnter<Break>()
.ActivateOnEnter<Flare>()
.ActivateOnEnter<UnholyDarkness>()
.ActivateOnEnter<FloodOfDarkness>()
.ActivateOnEnter<DelugeOfDarkness2>()
.ActivateOnEnter<StygianShadow>()
.ActivateOnEnter<Atomos>()
.ActivateOnEnter<DarkDominion>()
.ActivateOnEnter<ThirdArtOfDarknessCleave>()
.ActivateOnEnter<ThirdArtOfDarknessHyperFocusedParticleBeam>()
.ActivateOnEnter<ThirdArtOfDarknessMultiProngedParticleBeam>()
.ActivateOnEnter<GhastlyGloomCross>()
.ActivateOnEnter<GhastlyGloomDonut>()
.ActivateOnEnter<CurseOfDarkness>()
.ActivateOnEnter<DarkEnergyParticleBeam>()
.ActivateOnEnter<FloodOfDarknessAdd>()
.ActivateOnEnter<ChaosCondensedParticleBeam>()
.ActivateOnEnter<DiffusiveForceParticleBeam>()
.ActivateOnEnter<Phaser>()
.ActivateOnEnter<Excruciate>()
.ActivateOnEnter<ActivePivotParticleBeam>()
.ActivateOnEnter<LoomingChaos>();
}
}

// TODO: mechanic phase bounds
// TODO: flood bounds & squares
// TODO: particle concentration towers
// TODO: evil seed
// TODO: chaser beam
// TODO: tankswap hints?
[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1010, NameID = 13624)]
public class Ch01CloudOfDarkness(WorldState ws, Actor primary) : BossModule(ws, primary, DefaultCenter, InitialBounds)
{
public static readonly WPos DefaultCenter = new(100, 100);
public static readonly ArenaBoundsCircle InitialBounds = new(40);
public static readonly ArenaBoundsCustom Phase1Bounds = new(InitialBounds.Radius, new(BuildPhase1BoundsContour()));
public static readonly ArenaBoundsCustom Phase2Bounds = new(InitialBounds.Radius, BuildPhase2BoundsPoly());
public static readonly WPos Phase1Midpoint = DefaultCenter + (Phase1Bounds.Poly.Parts[0].Vertices[1] + Phase1Bounds.Poly.Parts[0].Vertices[3]) * 0.5f;

public static List<WDir> BuildPhase1BoundsContour()
{
// north 'diagonal' is at [+/-15, -37] (it almost intersects the initial circle - at x=15 z is ~37.08)
// the main diagonal is 20, rotated by 45 degrees, which means that side corners are at x=+/- 40/sqrt(2), z = -37 + 40/sqrt(2) - 15
var nz = -37;
var nx = 15;
var halfDiag = 40 / MathF.Sqrt(2);
var cz = nz + halfDiag - nx;
return [new(nx, nz), new(halfDiag, cz), new(0, cz + halfDiag), new(-halfDiag, cz), new(-nx, nz)];
}

public static RelSimplifiedComplexPolygon BuildPhase2BoundsPoly()
{
// mid is union of 4 rects
var midHalfWidth = 3;
var midHalfLength = 24;
var midOffset = 15;
var op1 = new PolygonClipper.Operand();
var op2 = new PolygonClipper.Operand();
op1.AddContour(CurveApprox.Rect(new WDir(0, +midOffset), new(1, 0), midHalfWidth, midHalfLength));
op1.AddContour(CurveApprox.Rect(new WDir(0, -midOffset), new(1, 0), midHalfWidth, midHalfLength));
op2.AddContour(CurveApprox.Rect(new WDir(+midOffset, 0), new(0, 1), midHalfWidth, midHalfLength));
op2.AddContour(CurveApprox.Rect(new WDir(-midOffset, 0), new(0, 1), midHalfWidth, midHalfLength));
var mid = InitialBounds.Clipper.Union(op1, op2);

// sides is union of two platforms and the outside ring
var sideHalfWidth = 7.5f;
var sideHalfLength = 10;
var sideOffset = 19 + sideHalfLength;
var sideRingWidth = 6;
op1.Clear();
op2.Clear();
op1.AddContour(CurveApprox.Rect(new WDir(+sideOffset, 0), new(1, 0), sideHalfWidth, sideHalfLength));
op1.AddContour(CurveApprox.Rect(new WDir(-sideOffset, 0), new(1, 0), sideHalfWidth, sideHalfLength));
op2.AddContour(CurveApprox.Circle(InitialBounds.Radius, 0.1f));
op2.AddContour(CurveApprox.Circle(InitialBounds.Radius - sideRingWidth, 0.1f));
var side = InitialBounds.Clipper.Union(op1, op2);

op1.Clear();
op2.Clear();
op1.AddPolygon(mid);
op2.AddPolygon(side);
return InitialBounds.Clipper.Union(op1, op2);
}
}

// envcontrols:
// 00 = main bounds telegraph
// - 00200010 - phase 1
// - 00020001 - phase 2
// - 00040004 - remove telegraph (note that actual bounds are controlled by something else!)
// 02 = outer ring
// - 00020001 - become dangerous
// - 00080004 - restore to normal
// 03-1E = mid squares
// - 08000001 - init
// - 00200010 - become occupied
// - 02000001 - become free
// - 00800040 - player is standing for too long, will break soon
// - 00080004 - break
// - 00020001 - repair
// - arrangement:
// 04 0B
// 03 05 06 07 0E 0D 0C 0A
// 08 0F
// 09 10
// 17 1E
// 16 1D
// 11 13 14 15 1C 1B 1A 18
// 12 19
// 1F-2E = 1-man towers
// - 00020001 - appear
// - 00200010 - occupied
// - 00080004 - disappear
// - 08000001 - ? (spot animation)
// - arrangement:
// 25 26
// 21 xx 1F xx xx 20 xx 22
// 23 24
// xx xx
// xx xx
// 2B 2C
// 29 xx 27 xx xx 28 xx 2A
// 2D 2E
// 2F-3E = 2-man towers
// - 00020001 - appear
// - 00200010 - occupied by 1
// - 00800040 - occupied by 2
// - 00080004 - disappear
// - 08000001 - ? (spot animations)
// - arrangement (also covers intersecting square):
// 35 36
// 31 xx 2F xx xx 30 xx 32
// 33 34
// xx xx
// xx xx
// 3B 3C
// 39 xx 37 xx xx 38 xx 3A
// 3D 3E
// 3F-46 = 3-man towers
// - 00020001 - appear
// - 00200010 - occupied by 1
// - 00800040 - occupied by 2
// - 02000100 - occupied by 3
// - 00080004 - disappear
// - 08000001 - ? (spot animations)
// - arrangement:
// 3F 43
// 42 40 44 46
// 41 45
// 47-56 = 1-man tower falling orb
// 57-66 = 2-man tower falling orb
// 67-6E = 3-man tower falling orb

Loading

0 comments on commit 18dff0e

Please sign in to comment.