Skip to content

Commit

Permalink
M3S
Browse files Browse the repository at this point in the history
  • Loading branch information
awgil committed Aug 8, 2024
1 parent 4d6d3fd commit efb8c9d
Show file tree
Hide file tree
Showing 13 changed files with 1,097 additions and 0 deletions.
14 changes: 14 additions & 0 deletions BossMod/Data/ActorEnumeration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,18 @@ public static IEnumerable<Actor> SortedByRange(this IEnumerable<Actor> range, WP

// find farthest actor from point
public static Actor? Farthest(this IEnumerable<Actor> range, WPos origin) => range.MaxBy(a => (a.Position - origin).LengthSq());

// count num actors matching and not matching a condition
public static (int match, int mismatch) CountByCondition(this IEnumerable<Actor> range, Func<Actor, bool> condition)
{
int match = 0, mismatch = 0;
foreach (var a in range)
{
if (condition(a))
++match;
else
++mismatch;
}
return (match, mismatch);
}
}
142 changes: 142 additions & 0 deletions BossMod/Modules/Dawntrail/Savage/RM03SBruteBomber/BarbarousBarrage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
namespace BossMod.Dawntrail.Savage.RM03SBruteBomber;

class BarbarousBarrageTowers(BossModule module) : Components.GenericTowers(module)
{
public enum State { None, NextNS, NextEW, NextCorners, NextCenter, Done }

public State CurState;

public override void DrawArenaForeground(int pcSlot, Actor pc)
{
base.DrawArenaForeground(pcSlot, pc);
// draw next towers to aim knockback
if (CurState != State.None)
foreach (var p in TowerPositions(CurState == State.NextNS ? State.NextCorners : CurState + 1))
Arena.AddCircle(p, 4, ArenaColor.Object);
}

public override void OnEventEnvControl(byte index, uint state)
{
if (CurState == State.None && index is 14 or 15)
SetState(index == 14 ? State.NextNS : State.NextEW, 4);
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
switch ((AID)spell.Action.ID)
{
case AID.BarbarousBarrageExplosion4:
SetState(State.NextCorners, 2);
break;
case AID.BarbarousBarrageExplosion2:
SetState(State.NextCenter, 8);
break;
case AID.BarbarousBarrageExplosion8:
SetState(State.Done, 1);
break;
}
}

private void SetState(State state, int soakers)
{
if (CurState != state)
{
CurState = state;
Towers.Clear();
foreach (var p in TowerPositions(state))
Towers.Add(new(p, 4, soakers, soakers));
}
}

private IEnumerable<WPos> TowerPositions(State state)
{
switch (state)
{
case State.NextNS:
yield return Module.Center + new WDir(0, -11);
yield return Module.Center + new WDir(0, +11);
break;
case State.NextEW:
yield return Module.Center + new WDir(-11, 0);
yield return Module.Center + new WDir(+11, 0);
break;
case State.NextCorners:
yield return Module.Center + new WDir(-11, -11);
yield return Module.Center + new WDir(-11, +11);
yield return Module.Center + new WDir(+11, -11);
yield return Module.Center + new WDir(+11, +11);
break;
case State.NextCenter:
yield return Module.Center;
break;
}
}
}

class BarbarousBarrageKnockback(BossModule module) : Components.Knockback(module)
{
private readonly BarbarousBarrageTowers? _towers = module.FindComponent<BarbarousBarrageTowers>();

private static readonly AOEShapeCircle _shape = new(4);

public override IEnumerable<Source> Sources(int slot, Actor actor)
{
if (_towers != null)
{
foreach (var t in _towers.Towers)
{
var dist = t.MinSoakers switch
{
4 => 23,
2 => 19,
8 => 15,
_ => 0
};
yield return new(t.Position, dist, default, _shape);
}
}
}
}

class BarbarousBarrageMurderousMist(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BarbarousBarrageMurderousMist), new AOEShapeCone(40, 135.Degrees()));

class BarbarousBarrageLariatCombo(BossModule module) : Components.GenericAOEs(module)
{
public readonly List<AOEInstance> AOEs = [];

private static readonly AOEShapeRect _shape = new(70, 17);

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

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
var (off1, off2) = (AID)spell.Action.ID switch
{
AID.BarbarousBarrageLariatComboFirstRR => (-90.Degrees(), -90.Degrees()),
AID.BarbarousBarrageLariatComboFirstRL => (-90.Degrees(), 90.Degrees()),
AID.BarbarousBarrageLariatComboFirstLR => (90.Degrees(), -90.Degrees()),
AID.BarbarousBarrageLariatComboFirstLL => (90.Degrees(), 90.Degrees()),
_ => default
};
if (off1 != default)
{
var from = caster.Position;
var to = spell.LocXZ;
var offset = 0.6667f * (to - from);
var dir1 = Angle.FromDirection(offset);
var dir2 = dir1 + 180.Degrees();
AOEs.Add(new(_shape, from - offset + 12 * (dir1 + off1).ToDirection(), dir1, Module.CastFinishAt(spell, 1.2f)));
AOEs.Add(new(_shape, to + offset + 12 * (dir2 + off2).ToDirection(), dir2, Module.CastFinishAt(spell, 5.6f)));
}
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.BarbarousBarrageLariatComboFirstRAOE or AID.BarbarousBarrageLariatComboFirstLAOE or AID.BarbarousBarrageLariatComboSecondRAOE or AID.BarbarousBarrageLariatComboSecondLAOE)
{
if (NumCasts < AOEs.Count && !spell.LocXZ.AlmostEqual(AOEs[NumCasts].Origin, 2))
ReportError($"Unexpected AOE: {spell.Location} vs {AOEs[NumCasts].Origin}");
++NumCasts;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace BossMod.Dawntrail.Savage.RM03SBruteBomber;

class BombarianSpecial(BossModule module) : Components.UniformStackSpread(module, 5, 5, alwaysShowSpreads: true)
{
public enum Mechanic { None, Spread, Pairs }

public Mechanic CurMechanic;

public void Show(float delay)
{
switch (CurMechanic)
{
case Mechanic.Spread:
AddSpreads(Raid.WithoutSlot(true), WorldState.FutureTime(delay));
break;
case Mechanic.Pairs:
// TODO: can target any role
AddStacks(Raid.WithoutSlot(true).Where(p => p.Class.IsSupport()), WorldState.FutureTime(delay));
break;
}
}

public override void AddGlobalHints(GlobalHints hints)
{
if (CurMechanic != Mechanic.None)
hints.Add(CurMechanic.ToString());
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
var mechanic = (AID)spell.Action.ID switch
{
AID.OctoboomBombarianSpecial => Mechanic.Spread,
AID.QuadroboomBombarianSpecial => Mechanic.Pairs,
_ => Mechanic.None
};
if (mechanic != Mechanic.None)
CurMechanic = mechanic;
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.BombariboomSpread or AID.BombariboomPair)
{
Spreads.Clear();
Stacks.Clear();
CurMechanic = Mechanic.None;
}
}
}

class BombarianSpecialRaidwide(BossModule module) : Components.CastCounter(module, default)
{
public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.BombarianSpecialRaidwide1 or AID.BombarianSpecialRaidwide2 or AID.BombarianSpecialRaidwide3 or AID.BombarianSpecialRaidwide4 or AID.BombarianSpecialRaidwide5 or AID.BombarianSpecialRaidwide6
or AID.SpecialBombarianSpecialRaidwide1 or AID.SpecialBombarianSpecialRaidwide2 or AID.SpecialBombarianSpecialRaidwide3 or AID.SpecialBombarianSpecialRaidwide4 or AID.SpecialBombarianSpecialRaidwide5 or AID.SpecialBombarianSpecialRaidwide6)
{
++NumCasts;
}
}
}

class BombarianSpecialOut(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BombarianSpecialOut), new AOEShapeCircle(10));
class BombarianSpecialIn(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BombarianSpecialIn), new AOEShapeDonut(6, 40));
class BombarianSpecialAOE(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BombarianSpecialAOE), new AOEShapeCircle(8));
class BombarianSpecialKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.BombarianSpecialKnockback), 10);
class SpecialBombarianSpecialOut(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SpecialBombarianSpecialOut), new AOEShapeCircle(10));
class SpecialBombarianSpecialIn(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SpecialBombarianSpecialIn), new AOEShapeDonut(6, 40));
34 changes: 34 additions & 0 deletions BossMod/Modules/Dawntrail/Savage/RM03SBruteBomber/Diveboom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace BossMod.Dawntrail.Savage.RM03SBruteBomber;

class OctoboomDiveProximity(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.OctoboomDiveProximityAOE), new AOEShapeCircle(20)); // TODO: verify falloff
class OctoboomDiveKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.OctoboomDiveKnockbackAOE), 25);
class QuadroboomDiveProximity(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.QuadroboomDiveProximityAOE), new AOEShapeCircle(20)); // TODO: verify falloff
class QuadroboomDiveKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.QuadroboomDiveKnockbackAOE), 25);

class Diveboom(BossModule module) : Components.UniformStackSpread(module, 5, 5, alwaysShowSpreads: true)
{
public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
switch ((AID)spell.Action.ID)
{
case AID.OctoboomDiveProximityAOE:
case AID.OctoboomDiveKnockbackAOE:
AddSpreads(Raid.WithoutSlot(true), Module.CastFinishAt(spell));
break;
case AID.QuadroboomDiveProximityAOE:
case AID.QuadroboomDiveKnockbackAOE:
// TODO: can target any role
AddStacks(Raid.WithoutSlot(true).Where(p => p.Class.IsSupport()), Module.CastFinishAt(spell));
break;
}
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.DiveboomSpread or AID.DiveboomPair)
{
Spreads.Clear();
Stacks.Clear();
}
}
}
66 changes: 66 additions & 0 deletions BossMod/Modules/Dawntrail/Savage/RM03SBruteBomber/FinalFusedown.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace BossMod.Dawntrail.Savage.RM03SBruteBomber;

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

private static readonly AOEShapeCircle _shape = new(8);

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => _aoes.Skip(NumCasts).Take(4);

public override void OnStatusGain(Actor actor, ActorStatus status)
{
var delay = (SID)status.ID switch
{
SID.FinalFusedownFutureSelfDestructShort => 12.2f,
SID.FinalFusedownFutureSelfDestructLong => 17.2f,
_ => 0
};
if (delay > 0)
{
_aoes.Add(new(_shape, actor.Position, default, WorldState.FutureTime(delay)));
_aoes.SortBy(aoe => aoe.Activation);
}
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID is AID.FinalFusedownSelfDestructShort or AID.FinalFusedownSelfDestructLong)
++NumCasts;
}
}

class FinalFusedownExplosion(BossModule module) : Components.GenericStackSpread(module, true)
{
public int NumCasts;
private readonly List<Spread> _spreads1 = [];
private readonly List<Spread> _spreads2 = [];

public void Show() => Spreads = _spreads1;

public override void OnStatusGain(Actor actor, ActorStatus status)
{
(List<Spread>? list, float delay) = (SID)status.ID switch
{
SID.FinalFusedownFutureExplosionShort => (_spreads1, 12.2f),
SID.FinalFusedownFutureExplosionLong => (_spreads2, 17.2f),
_ => (null, 0)
};
list?.Add(new(actor, 6, WorldState.FutureTime(delay)));
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
switch ((AID)spell.Action.ID)
{
case AID.FinalFusedownExplosionShort:
++NumCasts;
Spreads = _spreads2;
break;
case AID.FinalFusedownExplosionLong:
++NumCasts;
Spreads.Clear();
break;
}
}
}
52 changes: 52 additions & 0 deletions BossMod/Modules/Dawntrail/Savage/RM03SBruteBomber/FuseOrFoe.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
namespace BossMod.Dawntrail.Savage.RM03SBruteBomber;

class InfernalSpin(BossModule module) : Components.GenericRotatingAOE(module)
{
private static readonly AOEShapeCone _shape = new(40, 30.Degrees());

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
var increment = (AID)spell.Action.ID switch
{
AID.InfernalSpinFirstCW => -45.Degrees(),
AID.InfernalSpinFirstCCW => 45.Degrees(),
_ => default
};
if (increment != default)
{
Sequences.Add(new(_shape, caster.Position, spell.Rotation, increment, Module.CastFinishAt(spell, 0.5f), 1.1f, 8));
}
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (Sequences.Count > 0 && (AID)spell.Action.ID is AID.InfernalSpinFirstAOE or AID.InfernalSpinRestAOE)
{
AdvanceSequence(0, WorldState.CurrentTime);
}
}
}

class ExplosiveRain(BossModule module) : Components.ConcentricAOEs(module, _shapes)
{
private static readonly AOEShape[] _shapes = [new AOEShapeCircle(8), new AOEShapeDonut(8, 16), new AOEShapeDonut(16, 24)];

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.ExplosiveRain11 or AID.ExplosiveRain21)
AddSequence(caster.Position, Module.CastFinishAt(spell));
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
var order = (AID)spell.Action.ID switch
{
AID.ExplosiveRain11 or AID.ExplosiveRain21 => 0,
AID.ExplosiveRain12 or AID.ExplosiveRain22 => 1,
AID.ExplosiveRain13 or AID.ExplosiveRain23 => 2,
_ => -1
};
if (!AdvanceSequence(order, caster.Position, WorldState.FutureTime(4)))
ReportError($"Unexpected ring {order}");
}
}
Loading

0 comments on commit efb8c9d

Please sign in to comment.