Skip to content

Commit

Permalink
Ex2 module.
Browse files Browse the repository at this point in the history
  • Loading branch information
awgil committed Jul 10, 2024
1 parent 66cee43 commit a53d37e
Show file tree
Hide file tree
Showing 13 changed files with 1,050 additions and 1 deletion.
2 changes: 1 addition & 1 deletion BossMod/BossModule/ArenaBounds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public override WDir ClampToBounds(WDir offset)
}

// if rotation is 0, half-width is along X and half-height is along Z
public record class ArenaBoundsRect(float HalfWidth, float HalfHeight, Angle Rotation = default, float MapResolution = 0.5f) : ArenaBounds(MathF.Max(HalfWidth, HalfHeight), MapResolution)
public record class ArenaBoundsRect(float HalfWidth, float HalfHeight, Angle Rotation = default, float Radius = 0, float MapResolution = 0.5f) : ArenaBounds(Radius > 0 ? Radius : MathF.Max(HalfWidth, HalfHeight), MapResolution)
{
public readonly WDir Orientation = Rotation.ToDirection();

Expand Down
21 changes: 21 additions & 0 deletions BossMod/Modules/Dawntrail/Extreme/Ex2ZoraalJa/AeroIII.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace BossMod.Dawntrail.Extreme.Ex2ZoraalJa;

class AeroIII(BossModule module) : Components.Knockback(module, ignoreImmunes: true)
{
public readonly IReadOnlyList<Actor> Voidzones = module.Enemies(OID.BitingWind);

private static readonly AOEShapeCircle _shape = new(4);

public override IEnumerable<Source> Sources(int slot, Actor actor)
{
foreach (var v in Voidzones)
yield return new(v.Position, 25, Shape: _shape);
}

public override void DrawArenaForeground(int pcSlot, Actor pc)
{
foreach (var v in Voidzones)
_shape.Outline(Arena, v.Position);
base.DrawArenaForeground(pcSlot, pc);
}
}
49 changes: 49 additions & 0 deletions BossMod/Modules/Dawntrail/Extreme/Ex2ZoraalJa/BitterWhirlwind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace BossMod.Dawntrail.Extreme.Ex2ZoraalJa;

// TODO: generalize to 'aoe tankswap tankbuster'
class BitterWhirlwind(BossModule module) : Components.GenericBaitAway(module, centerAtTarget: true)
{
private Actor? _source;
private ulong _prevTarget;
private DateTime _activation;

private static readonly AOEShapeCircle _shape = new(5);

public override void Update()
{
CurrentBaits.Clear();
if (_source != null)
{
var target = WorldState.Actors.Find(_source.CastInfo?.TargetID ?? Module.PrimaryActor.TargetID);
if (target != null)
{
CurrentBaits.Add(new(Module.PrimaryActor, target, _shape, _activation));
}
}
}

public override void AddHints(int slot, Actor actor, TextHints hints)
{
if (CurrentBaits.Any(b => b.Target.InstanceID == _prevTarget) && actor.Role == Role.Tank)
hints.Add(_prevTarget != actor.InstanceID ? "Taunt!" : "Pass aggro!");
base.AddHints(slot, actor, hints);
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.BitterWhirlwindAOEFirst)
{
_source = caster;
_activation = spell.NPCFinishAt;
}
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.BitterWhirlwindAOEFirst or AID.BitterWhirlwindAOERest)
{
++NumCasts;
_prevTarget = spell.MainTargetID;
}
}
}
108 changes: 108 additions & 0 deletions BossMod/Modules/Dawntrail/Extreme/Ex2ZoraalJa/ChasmOfVollok.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
namespace BossMod.Dawntrail.Extreme.Ex2ZoraalJa;

class ChasmOfVollokFangSmall(BossModule module) : Components.GenericAOEs(module, ActionID.MakeSpell(AID.ChasmOfVollokFangSmallAOE))
{
public readonly List<AOEInstance> AOEs = [];

private static readonly AOEShapeRect _shape = new(2.5f, 2.5f, 2.5f);

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

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.ChasmOfVollokFangSmall)
{
// the visual cast happens on one of the side platforms at intercardinals, offset by 30
var platformOffset = 30 / 1.41421356f;
var offset = new WDir(caster.Position.X > Module.Center.X ? -platformOffset : +platformOffset, caster.Position.Z > Module.Center.Z ? -platformOffset : +platformOffset);
AOEs.Add(new(_shape, caster.Position + offset, spell.Rotation, spell.NPCFinishAt));
}
}
}

// note: we can start showing aoes earlier, right when fang actors spawn
class ChasmOfVollokFangLarge(BossModule module) : Components.GenericAOEs(module, ActionID.MakeSpell(AID.ChasmOfVollokFangLargeAOE))
{
public readonly List<AOEInstance> AOEs = [];

private static readonly AOEShapeRect _shape = new(5, 5, 5);

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

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.VollokLargeAOE)
{
AOEs.Add(new(_shape, caster.Position, spell.Rotation, spell.NPCFinishAt));

var mainOffset = Ex2ZoraalJa.NormalCenter - Module.Center;
var fangOffset = caster.Position - Module.Center;
var mirrorOffset = fangOffset.Dot(mainOffset) > 0 ? -2 * mainOffset : 2 * mainOffset;
AOEs.Add(new(_shape, caster.Position + mirrorOffset, spell.Rotation, spell.NPCFinishAt));
}
}
}

class ChasmOfVollokPlayer(BossModule module) : Components.GenericAOEs(module, ActionID.MakeSpell(AID.ChasmOfVollokPlayer), "GTFO from occupied cell!")
{
public bool Active;
private readonly List<Actor> _targets = [];
private DateTime _activation;

private static readonly AOEShapeRect _shape = new(2.5f, 2.5f, 2.5f);
private static readonly WDir _localX = (-135).Degrees().ToDirection();
private static readonly WDir _localZ = 135.Degrees().ToDirection();

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
if (!Active)
yield break;
var platformOffset = 2 * (Module.Center - Ex2ZoraalJa.NormalCenter);
foreach (var t in _targets.Exclude(actor))
{
var playerOffset = t.Position - Ex2ZoraalJa.NormalCenter;
var playerX = _localX.Dot(playerOffset);
var playerZ = _localZ.Dot(playerOffset);
if (Math.Abs(playerX) >= 15 || Math.Abs(playerZ) >= 15)
{
playerOffset -= platformOffset;
playerX = _localX.Dot(playerOffset);
playerZ = _localZ.Dot(playerOffset);
}
var cellX = CoordinateToCell(playerX);
var cellZ = CoordinateToCell(playerZ);
var cellCenter = Ex2ZoraalJa.NormalCenter + _localX * CellCenterCoordinate(cellX) + _localZ * CellCenterCoordinate(cellZ);

yield return new(_shape, cellCenter, 45.Degrees(), _activation);
if (platformOffset != default)
yield return new(_shape, cellCenter + platformOffset, 45.Degrees(), _activation);
}
}

public override void Update()
{
// assume that if player dies, he won't participate in the mechanic
_targets.RemoveAll(t => t.IsDead);
}

public override PlayerPriority CalcPriority(int pcSlot, Actor pc, int playerSlot, Actor player, ref uint customColor) => PlayerPriority.Normal;

public override void OnEventIcon(Actor actor, uint iconID)
{
if (iconID == (uint)IconID.ChasmOfVollok)
{
_targets.Add(actor);
_activation = WorldState.FutureTime(6.1f);
}
}

private int CoordinateToCell(float x) => x switch
{
< -5 => 0,
< 0 => 1,
< 5 => 2,
_ => 3
};

private float CellCenterCoordinate(int c) => -7.5f + c * 5;
}
42 changes: 42 additions & 0 deletions BossMod/Modules/Dawntrail/Extreme/Ex2ZoraalJa/DrumOfVollok.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace BossMod.Dawntrail.Extreme.Ex2ZoraalJa;

class DrumOfVollokPlatforms(BossModule module) : BossComponent(module)
{
public bool Active;

public override void OnEventEnvControl(byte index, uint state)
{
if (index != 11)
return;

switch (state)
{
case 0x00800040:
Module.Arena.Bounds = Ex2ZoraalJa.NWPlatformBounds;
Module.Arena.Center += 15 * 135.Degrees().ToDirection();
Active = true;
break;
case 0x02000100:
Module.Arena.Bounds = Ex2ZoraalJa.NEPlatformBounds;
Module.Arena.Center += 15 * (-135).Degrees().ToDirection();
Active = true;
break;
}
}
}

class DrumOfVollok(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.DrumOfVollokAOE), 4, 2, 2);

class DrumOfVollokKnockback(BossModule module) : Components.Knockback(module, ignoreImmunes: true)
{
private readonly DrumOfVollok? _main = module.FindComponent<DrumOfVollok>();

public override IEnumerable<Source> Sources(int slot, Actor actor)
{
if (_main == null || _main.Stacks.Any(s => s.Target == actor))
yield break;
foreach (var s in _main.Stacks)
if (actor.Position.InCircle(s.Target.Position, s.Radius))
yield return new(s.Target.Position, 25, s.Activation);
}
}
19 changes: 19 additions & 0 deletions BossMod/Modules/Dawntrail/Extreme/Ex2ZoraalJa/DutysEdge.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace BossMod.Dawntrail.Extreme.Ex2ZoraalJa;

// TODO: create and use generic 'line stack' component
class DutysEdge(BossModule module) : Components.GenericWildCharge(module, 4, ActionID.MakeSpell(AID.DutysEdgeAOE), 100)
{
public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
switch ((AID)spell.Action.ID)
{
case AID.DutysEdgeTarget:
foreach (var (i, p) in Raid.WithSlot(true))
PlayerRoles[i] = p.InstanceID == spell.MainTargetID ? PlayerRole.Target : PlayerRole.Share;
break;
case AID.DutysEdgeAOE:
++NumCasts;
break;
}
}
}
28 changes: 28 additions & 0 deletions BossMod/Modules/Dawntrail/Extreme/Ex2ZoraalJa/Ex2ZoraalJa.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace BossMod.Dawntrail.Extreme.Ex2ZoraalJa;

class MultidirectionalDivide(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MultidirectionalDivide), new AOEShapeCross(30, 2));
class MultidirectionalDivideMain(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MultidirectionalDivideMain), new AOEShapeCross(30, 4));
class MultidirectionalDivideExtra(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MultidirectionalDivideExtra), new AOEShapeCross(40, 2));
class RegicidalRage(BossModule module) : Components.TankbusterTether(module, ActionID.MakeSpell(AID.RegicidalRageAOE), (uint)TetherID.RegicidalRage, 8);
class BurningChains(BossModule module) : Components.Chains(module, (uint)TetherID.BurningChains, ActionID.MakeSpell(AID.BurningChainsAOE));
class HalfCircuitRect(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HalfCircuitAOERect), new AOEShapeRect(60, 60));
class HalfCircuitDonut(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HalfCircuitAOEDonut), new AOEShapeDonut(10, 30));
class HalfCircuitCircle(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HalfCircuitAOECircle), new AOEShapeCircle(10));

[ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 996, PlanLevel = 100)]
public class Ex2ZoraalJa(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), NormalBounds)
{
public static readonly WPos NormalCenter = new(100, 100);
public static readonly ArenaBoundsRect NormalBounds = new(20, 20, 45.Degrees());
public static readonly ArenaBoundsRect SmallBounds = new(10, 10, 45.Degrees(), 20);
public static readonly ArenaBoundsCustom NWPlatformBounds = BuildTwoPlatformsBounds(135.Degrees());
public static readonly ArenaBoundsCustom NEPlatformBounds = BuildTwoPlatformsBounds(-135.Degrees());

private static ArenaBoundsCustom BuildTwoPlatformsBounds(Angle orientation)
{
var dir = orientation.ToDirection();
var main = new PolygonClipper.Operand(CurveApprox.Rect(dir, 10, 10).Select(p => p - 15 * dir));
var side = new PolygonClipper.Operand(CurveApprox.Rect(dir, 10, 10).Select(p => p + 15 * dir));
return new(20, NormalBounds.Clipper.Union(main, side));
}
}
Loading

0 comments on commit a53d37e

Please sign in to comment.