Skip to content

Commit

Permalink
Autoduty fun: vanguard
Browse files Browse the repository at this point in the history
  • Loading branch information
awgil committed Aug 22, 2024
1 parent 400e926 commit 3540b27
Show file tree
Hide file tree
Showing 10 changed files with 477 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
namespace BossMod.Dawntrail.Dungeon.D04Vanguard.D041VanguardCommander;

public enum OID : uint
{
Boss = 0x411D, // R3.240, x1
Helper = 0x233C, // R0.500, x7, Helper type
VanguardSentryR8 = 0x41BC, // R3.240, x0 (spawn during fight)
}

public enum AID : uint
{
AutoAttack = 36403, // Boss->player, no cast, single-target
Electrowave = 36571, // Boss->self, 5.0s cast, range 60 circle, raidwide
EnhancedMobilityROut = 36559, // Boss->location, 10.0s cast, range 14 width 6 rect, teleport (right hand + move so that out is safe)
EnhancedMobilityRIn = 36560, // Boss->location, 10.0s cast, range 14 width 6 rect, teleport (right hand + move so that in is safe)
EnhancedMobilityLIn = 39140, // Boss->location, 10.0s cast, range 14 width 6 rect (right hand + move so that in is safe)
EnhancedMobilityLOut = 39141, // Boss->location, 10.0s cast, range 14 width 6 rect (left hand + move so that in is safe)
EnhancedMobilityAOEROut = 36563, // Helper->self, 10.5s cast, range 10 width 14 rect
EnhancedMobilityAOELOut = 36564, // Helper->self, 10.5s cast, range 10 width 14 rect
EnhancedMobilityAOERIn = 37184, // Helper->self, 10.5s cast, range 20 width 14 rect
EnhancedMobilityAOELIn = 37191, // Helper->self, 10.5s cast, range 20 width 14 rect
RapidRotaryROut = 36561, // Boss->self, no cast, single-target, visual
RapidRotaryRIn = 36562, // Boss->self, no cast, single-target, visual
RapidRotaryLIn = 39142, // Boss->self, no cast, single-target, visual
RapidRotaryLOut = 39143, // Boss->self, no cast, single-target, visual
RapidRotaryAOE = 36565, // Helper->self, no cast, range 11-17 donut 120-degree cone (common to in & out)
RapidRotaryAOEOut = 36566, // Helper->self, no cast, range 14 120-degree cone
RapidRotaryAOEIn = 36567, // Helper->self, no cast, range 14-28 donut 120-degree cone
Dispatch = 36568, // Boss->self, 4.0s cast, single-target, visual (spawn sentries)
Rush = 36569, // VanguardSentryR8->location, 6.0s cast, width 5 rect charge
AerialOffensive = 36570, // VanguardSentryR8->location, 9.0s cast, range 4+10 circle puddle
Electrosurge = 36572, // Boss->self, 4.0+1.0s cast, single-target, visual (spread)
ElectrosurgeAOE = 36573, // Helper->player, 5.0s cast, range 5 circle spread
}

public enum IconID : uint
{
Electrosurge = 315, // player
}

class Electrowave(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.Electrowave));

class EnhancedMobility(BossModule module) : Components.GenericAOEs(module)
{
private readonly List<AOEInstance> _aoes = [];

private static readonly AOEShapeRect _shapeSideOut = new(10, 7);
private static readonly AOEShapeRect _shapeSideIn = new(20, 7);
private static readonly AOEShape _shapeOut = new AOEShapeCone(17, 60.Degrees());
private static readonly AOEShape _shapeIn = new AOEShapeDonutSector(11, 28, 60.Degrees());

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

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
(AOEShape? side, AOEShape? main, float offset, Angle rotation) = (AID)spell.Action.ID switch
{
AID.EnhancedMobilityAOEROut => (_shapeSideOut, _shapeOut, +7, -60.Degrees()),
AID.EnhancedMobilityAOELOut => (_shapeSideOut, _shapeOut, -7, 60.Degrees()),
AID.EnhancedMobilityAOERIn => (_shapeSideIn, _shapeIn, +7, 60.Degrees()),
AID.EnhancedMobilityAOELIn => (_shapeSideIn, _shapeIn, -7, -60.Degrees()),
_ => (null, null, 0, default)
};
if (side != null && main != null)
{
var activation = Module.CastFinishAt(spell);
_aoes.Add(new(side, caster.Position + offset * spell.Rotation.ToDirection().OrthoR(), spell.Rotation, activation));
_aoes.Add(new(main, caster.Position, spell.Rotation + rotation, activation.AddSeconds(1.3f)));
_aoes.Add(new(main, caster.Position, spell.Rotation + rotation * 3, activation.AddSeconds(1.6f)));
_aoes.Add(new(main, caster.Position, spell.Rotation + rotation * 5, activation.AddSeconds(1.9f)));
}
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.EnhancedMobilityAOEROut or AID.EnhancedMobilityAOELOut or AID.EnhancedMobilityAOERIn or AID.EnhancedMobilityAOELIn or AID.RapidRotaryAOEOut or AID.RapidRotaryAOEIn && _aoes.Count > 0)
_aoes.RemoveAt(0);
}
}

class Rush(BossModule module) : Components.ChargeAOEs(module, ActionID.MakeSpell(AID.Rush), 2.5f);
class AerialOffensive(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.AerialOffensive), 14, maxCasts: 4);
class Electrosurge(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.ElectrosurgeAOE), 5);

class D041VanguardCommanderStates : StateMachineBuilder
{
public D041VanguardCommanderStates(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<Electrowave>()
.ActivateOnEnter<EnhancedMobility>()
.ActivateOnEnter<Rush>()
.ActivateOnEnter<AerialOffensive>()
.ActivateOnEnter<Electrosurge>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 831, NameID = 12750)]
public class D041VanguardCommander(WorldState ws, Actor primary) : BossModule(ws, primary, new(-100, 207), new ArenaBoundsSquare(17));
202 changes: 202 additions & 0 deletions BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D042Protector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
namespace BossMod.Dawntrail.Dungeon.D04Vanguard.D042Protector;

public enum OID : uint
{
Boss = 0x4237, // R5.830, x1
LaserTurret = 0x4238, // R0.960, x16
ExplosiveTurret = 0x4239, // R0.960, x8
FulminousFence = 0x4255, // R1.000, x4
Helper = 0x233C, // R0.500, x7, Helper type
}

public enum AID : uint
{
AutoAttack = 878, // Boss->player, no cast, single-target
Electrowave = 37161, // Boss->self, 5.0s cast, range 50 circle, raidwide
SearchAndDestroy = 37154, // Boss->self, 3.0s cast, single-target, visual (turrets)
HomingCannon = 37155, // LaserTurret->self, 2.5s cast, range 50 width 2 rect
Shock = 37156, // ExplosiveTurret->location, 2.5s cast, range 3 circle
SearchAndDestroyEnd = 37153, // Boss->self, no cast, single-target, visual (mechanic end)
FulminousFence = 37149, // Boss->self, 3.0s cast, single-target, visual (barriers)
ElectrostaticContact = 37158, // FulminousFence->player, no cast, single-target, damage + paralyze if hit by fence
BatteryCircuit = 37159, // Boss->self, 5.0s cast, single-target, visual (rotating aoe)
BatteryCircuitAOEFirst = 37351, // Helper->self, 5.0s cast, range 30 30-degree cone
BatteryCircuitAOERest = 37344, // Helper->self, no cast, range 30 30-degree cone
ElectrowhirlFirst = 37350, // Helper->self, 5.0s cast, range 6 circle
ElectrowhirlRest = 37160, // Helper->self, 3.0s cast, range 6 circle
Bombardment = 39016, // Helper->location, 3.0s cast, range 5 circle
RapidThunder = 37162, // Boss->player, 5.0s cast, single-target, tankbuster
MotionSensor = 37150, // Boss->self, 3.0s cast, single-target, visual (acceleration bombs)
MotionSensorApply = 37343, // Helper->player, no cast, single-target, visual (apply bomb debuff)
BlastCannon = 37151, // LaserTurret->self, 3.0s cast, range 26 width 4 rect
HeavyBlastCannon = 37345, // Boss->self/players, 8.0s cast, range 36 width 8 rect, line stack
HeavyBlastCannonTargetSelect = 37347, // Helper->player, no cast, single-target, target select
TrackingBolt = 37348, // Boss->self, 8.0s cast, single-target, visual (spread)
TrackingBoltAOE = 37349, // Helper->player, 8.0s cast, range 8 circle spread
}

public enum SID : uint
{
AccelerationBomb1 = 3802, // Helper->player, extra=0x0
AccelerationBomb2 = 4144, // Helper->player, extra=0x0
}

public enum IconID : uint
{
RotateCW = 167, // Boss
RapidThunder = 218, // player
MotionSensor = 267, // player
TrackingBolt = 196, // player
}

class Electrowave(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.Electrowave));
class HomingCannon(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HomingCannon), new AOEShapeRect(50, 1));
class Shock(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Shock), 3);

class FulminousFence(BossModule module) : BossComponent(module)
{
public record struct Line(WDir A, WDir B);

private Line[] _curPattern = [];
private float _curPatternZMult; // each pattern can be mirrored

private static readonly Line[] _pattern1 = [
new(new(-4, -12), new(+4, -12)), new(new(+4, -12), new(+4, -4)), new(new(+4, -12), new(+12, -12)), new(new(+12, -12), new(+12, -4)), new(new(+12, -4), new(+12, +4)), new(new(+12, +4), new(+4, +4)),
new(new(+4, +12), new(-4, +12)), new(new(-4, +12), new(-4, +4)), new(new(-4, +12), new(-12, +12)), new(new(-12, +12), new(-12, +4)), new(new(-12, +4), new(-12, -4)), new(new(-12, -4), new(-4, -4))
];
private static readonly Line[] _pattern2 = [
new(new(0, -12), new(0, -8)), new(new(-8, -8), new(-4, -4)), new(new(+8, -8), new(+4, -4)), new(new(0, +4), new(0, +8)), new(new(-8, +8), new(-12, +12)), new(new(+8, +8), new(+12, +12))
];

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
foreach (var (a, b) in ActiveLines())
{
var raw = ShapeDistance.Rect(a, b, 0);
hints.AddForbiddenZone(p => raw(p) - 1);
}
}

public override void DrawArenaForeground(int pcSlot, Actor pc)
{
foreach (var (a, b) in ActiveLines())
Arena.AddLine(a, b, ArenaColor.Danger, 2);
}

public override void OnEventEnvControl(byte index, uint state)
{
if (index != 13)
return;
switch (state)
{
case 0x00020001:
_curPattern = _pattern1;
_curPatternZMult = 1;
break;
case 0x00200010:
_curPattern = _pattern1;
_curPatternZMult = -1;
break;
case 0x01000080:
_curPattern = _pattern2;
_curPatternZMult = 1;
break;
case 0x08000400:
_curPattern = _pattern2;
_curPatternZMult = -1;
break;
case 0x00080004:
case 0x00400004:
case 0x02000004:
case 0x10000004:
_curPattern = [];
_curPatternZMult = 0;
break;
}
}

private IEnumerable<(WPos a, WPos b)> ActiveLines() => _curPattern.Select(l => (ConvertEndpoint(l.A), ConvertEndpoint(l.B)));
private WPos ConvertEndpoint(WDir p) => new(Module.Center.X + p.X, Module.Center.Z + p.Z * _curPatternZMult);
}

// note: never seen ccw rotation, assume it's not possible
class BatteryCircuit(BossModule module) : Components.GenericRotatingAOE(module)
{
private static readonly AOEShapeCone _shape = new(30, 15.Degrees());

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.BatteryCircuitAOEFirst)
Sequences.Add(new(_shape, caster.Position, spell.Rotation, -11.Degrees(), Module.CastFinishAt(spell), 0.5f, 33, 10));
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.BatteryCircuitAOEFirst or AID.BatteryCircuitAOERest)
AdvanceSequence(caster.Position, caster.Rotation, WorldState.CurrentTime);
}
}

class ElectrowhirlFirst(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ElectrowhirlFirst), new AOEShapeCircle(6));
class ElectrowhirlRest(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.ElectrowhirlRest), new AOEShapeCircle(6));
class Bombardment(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Bombardment), 5);
class RapidThunder(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.RapidThunder));

class MotionSensor(BossModule module) : Components.StayMove(module)
{
private readonly DateTime[] _expire = new DateTime[4];

public override void Update()
{
base.Update();
var deadline = WorldState.FutureTime(3);
for (int i = 0; i < _expire.Length; ++i)
Requirements[i] = _expire[i] != default && _expire[i] < deadline ? Requirement.Stay : Requirement.None;
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (Requirements[slot] == Requirement.Stay)
hints.ForcedMovement = new();
}

public override void OnStatusGain(Actor actor, ActorStatus status)
{
if ((SID)status.ID is SID.AccelerationBomb1 or SID.AccelerationBomb2 && Raid.FindSlot(actor.InstanceID) is var slot && slot >= 0)
_expire[slot] = status.ExpireAt;
}

public override void OnStatusLose(Actor actor, ActorStatus status)
{
if ((SID)status.ID is SID.AccelerationBomb1 or SID.AccelerationBomb2 && Raid.FindSlot(actor.InstanceID) is var slot && slot >= 0)
_expire[slot] = default;
}
}

class BlastCannon(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BlastCannon), new AOEShapeRect(26, 2));
class HeavyBlastCannon(BossModule module) : Components.SimpleLineStack(module, 4, 36, ActionID.MakeSpell(AID.HeavyBlastCannonTargetSelect), ActionID.MakeSpell(AID.HeavyBlastCannon), 8);
class TrackingBolt(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.TrackingBoltAOE), 8);

class D042ProtectorStates : StateMachineBuilder
{
public D042ProtectorStates(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<Electrowave>()
.ActivateOnEnter<HomingCannon>()
.ActivateOnEnter<Shock>()
.ActivateOnEnter<FulminousFence>()
.ActivateOnEnter<BatteryCircuit>()
.ActivateOnEnter<ElectrowhirlFirst>()
.ActivateOnEnter<ElectrowhirlRest>()
.ActivateOnEnter<Bombardment>()
.ActivateOnEnter<RapidThunder>()
.ActivateOnEnter<MotionSensor>()
.ActivateOnEnter<BlastCannon>()
.ActivateOnEnter<HeavyBlastCannon>()
.ActivateOnEnter<TrackingBolt>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 831, NameID = 12757)]
public class D042Protector(WorldState ws, Actor primary) : BossModule(ws, primary, new(0, -100), new ArenaBoundsRect(12, 20));
Loading

0 comments on commit 3540b27

Please sign in to comment.