Skip to content

Commit

Permalink
Merge pull request #388 from FFXIV-CombatReborn/mergeWIP
Browse files Browse the repository at this point in the history
The mightiest shield module
  • Loading branch information
CarnifexOptimus authored Oct 5, 2024
2 parents c8d19e1 + 4cfd97b commit 7eb09ce
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 25 deletions.
20 changes: 10 additions & 10 deletions BossMod/Components/DirectionalParry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// generic 'directional parry' component that shows actors and sides it's forbidden to attack them from
// uses common status + custom prediction
public class DirectionalParry(BossModule module, uint actorOID) : Adds(module, actorOID)
public class DirectionalParry(BossModule module, uint[] actorOID) : AddsMulti(module, actorOID)
{
[Flags]
public enum Side
Expand All @@ -17,13 +17,13 @@ public enum Side

public const uint ParrySID = 680; // common 'directional parry' status

private readonly Dictionary<ulong, int> _actorStates = []; // value == active-side | (imminent-side << 4)
public bool Active => _actorStates.Values.Any(s => ActiveSides(s) != Side.None);
public readonly Dictionary<ulong, int> ActorStates = []; // value == active-side | (imminent-side << 4)
public bool Active => ActorStates.Values.Any(s => ActiveSides(s) != Side.None);

public override void AddHints(int slot, Actor actor, TextHints hints)
{
var target = Actors.FirstOrDefault(w => w.InstanceID == actor.TargetID);
if (target != null && _actorStates.TryGetValue(actor.TargetID, out var targetState))
if (target != null && ActorStates.TryGetValue(actor.TargetID, out var targetState))
{
var forbiddenSides = ActiveSides(targetState) | ImminentSides(targetState);
var attackDir = (actor.Position - target.Position).Normalized();
Expand All @@ -46,7 +46,7 @@ public override void DrawArenaForeground(int pcSlot, Actor pc)
base.DrawArenaForeground(pcSlot, pc);
foreach (var a in ActiveActors)
{
if (_actorStates.TryGetValue(a.InstanceID, out var aState))
if (ActorStates.TryGetValue(a.InstanceID, out var aState))
{
var active = ActiveSides(aState);
var imminent = ImminentSides(aState);
Expand All @@ -64,7 +64,7 @@ public override void OnStatusGain(Actor actor, ActorStatus status)
{
// TODO: front+back is 3, left+right is C, but I don't really know which is which, didn't see examples yet...
// remove any predictions
_actorStates[actor.InstanceID] = status.Extra & 0xF;
ActorStates[actor.InstanceID] = status.Extra & 0xF;
}
}

Expand Down Expand Up @@ -96,14 +96,14 @@ private void DrawParry(Actor actor, Angle offset, uint color = 0)
MiniArena.PathStroke(false, color);
}

private int ActorState(ulong instanceID) => _actorStates.GetValueOrDefault(instanceID, 0);
public int ActorState(ulong instanceID) => ActorStates.GetValueOrDefault(instanceID, 0);

private void UpdateState(ulong instanceID, int state)
public void UpdateState(ulong instanceID, int state)
{
if (state == 0)
_actorStates.Remove(instanceID);
ActorStates.Remove(instanceID);
else
_actorStates[instanceID] = state;
ActorStates[instanceID] = state;
}

private static Side ActiveSides(int state) => (Side)(state & (int)Side.All);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,4 @@ protected override void DrawEnemies(int pcSlot, Actor pc)
}

[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", PrimaryActorOID = (uint)OID.BossP2, GroupType = BossModuleInfo.GroupType.Quest, GroupID = 70359, NameID = 13046, SortOrder = 2)]
public class DreamsOfANewDayP2(WorldState ws, Actor primary) : DreamsOfANewDay(ws, primary);
public class DreamsOfANewDayP2(WorldState ws, Actor primary) : DreamsOfANewDay(ws, primary);
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public HeroesAndPretendersStates(BossModule module) : base(module)
}
}

[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.Quest, GroupID = 70383, NameID = 13176, SortOrder = 1)]
[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.Quest, GroupID = 70383, NameID = 13176)]
public class HeroesAndPretenders(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena)
{
private static readonly ArenaBoundsComplex arena = new([new Polygon(new(676, 41), 14.5f, 20)]);
Expand Down
233 changes: 233 additions & 0 deletions BossMod/Modules/Dawntrail/Quest/RoleQuests/TheMightiestShield.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
namespace BossMod.Dawntrail.Quest.RoleQuests.TheMightiestShield;

public enum OID : uint
{
Boss = 0x43C7, // R5.4
CrackedMettle1 = 0x43E1, // R2.0
CrackedMettle2 = 0x43E4, // R4.86
UnyieldingMettle1 = 0x43E2, // R1.0
UnyieldingMettle2 = 0x43E0, // R2.0
UnyieldingMettle3 = 0x43E3, // R4.86
CravenFollower1 = 0x43CF, // R2.0
CravenFollower2 = 0x43CB, // R2.0
CravenFollower3 = 0x43C9, // R2.0
CravenFollower4 = 0x43CD, // R2.0
CravenFollower5 = 0x43D0, // R0.5
CravenFollower6 = 0x43CE, // R0.5
CravenFollower7 = 0x43CC, // R0.5
CravenFollower8 = 0x43CA, // R0.5
MagitekMissile = 0x43C8, // R1.0
Helper = 0x233C
}

public enum AID : uint
{
AutoAttack = 39025, // Boss->Kaqool, no cast, single-target

PhotonStream = 37001, // CravenFollower3/CravenFollower1/CravenFollower2/CravenFollower4->Kaqool, no cast, single-target
HomingLaserMarker = 38622, // CravenFollower1/CravenFollower4/CravenFollower2->player/Kaqool, 5.0s cast, single-target
HomingLaser = 38624, // CravenFollower1/CravenFollower4/CravenFollower2->player/Kaqool, no cast, range 42 width 8 rect
MissileStormVisual = 38601, // Boss->self, no cast, single-target
MissileStorm = 38602, // Helper->self, no cast, range 60 circle
AreaBombardmentVisual = 38599, // Boss->self, 7.0s cast, single-target
AreaBombardment = 38600, // Helper->self, 7.0s cast, range 4 circle
MagitekCannon = 39006, // CravenFollower3->player, 5.0s cast, range 6 circle, spread

GuidedMissileVisual1 = 38603, // Boss->self, no cast, single-target
GuidedMissileVisual2 = 35763, // MagitekMissile->self, no cast, single-target
GuidedMissileVisual3 = 35764, // MagitekMissile->self, no cast, single-target
Burst = 38598, // MagitekMissile->self, no cast, range 3 circle

SteelhogsMettle = 38621, // Boss->self, 3.0s cast, single-target
SteelhogsRevenge = 38625, // UnyieldingMettle1->self, 7.0s cast, range 12 circle

HeavySurfaceMissilesVisual = 38606, // Boss->self, 3.0s cast, single-target, damage fall off aoes
HeavySurfaceMissiles1 = 38607, // Helper->self, 7.0s cast, range 60 circle
HeavySurfaceMissiles2 = 38608, // Helper->self, 10.0s cast, range 60 circle
HeavySurfaceMissiles3 = 38609, // Helper->self, 13.0s cast, range 60 circle
HeavySurfaceMissiles4 = 38610, // Helper->self, 16.0s cast, range 60 circle

GunsBlazing1 = 38614, // Boss->self, 5.0s cast, single-target
GunsBlazing2 = 38616, // Boss->self, 5.0s cast, single-target
NeedleGun1 = 38618, // Helper->self, 5.0s cast, range 40 90-degree cone
NeedleGun2 = 38712, // Helper->self, 7.0s cast, range 40 90-degree cone
OilShower1 = 38619, // Helper->self, 7.0s cast, range 40 270-degree cone
OilShower2 = 38711, // Helper->self, 5.0s cast, range 40 270-degree cone

RuthlessBombardmentVisual = 38611, // Boss->self, 7.0s cast, single-target
RuthlessBombardment1 = 38613, // Helper->self, 7.0s cast, range 8 circle
RuthlessBombardment2 = 38612, // Helper->self, 7.0s cast, range 4 circle

Visual1 = 38615, // Boss->self, no cast, single-target
Visual2 = 38617, // Boss->self, no cast, single-target

SurfaceMissile1 = 38604, // Boss->self, no cast, single-target
SurfaceMissile2 = 38605, // Helper->location, 5.0s cast, range 6 circle
UnbreakableCermetDrill = 38620, // Boss->Kaqool, no cast, single-target

RagingArtilleryVisual1 = 38741, // Boss->self, 5.0s cast, single-target
RagingArtilleryVisual2 = 38743, // Boss->self, no cast, single-target
RagingArtilleryFirst = 38742, // Helper->self, 5.0s cast, range 60 circle, multiple raidwides
RagingArtilleryRest = 38744 // Helper->self, no cast, range 60 circle
}

public enum SID : uint
{
RightwardFracture = 4036, // none->CrackedMettle1/CrackedMettle2, extra=0x0
LeftwardFracture = 4037, // none->CrackedMettle1, extra=0x0
BackwardFracture = 4039 // none->CrackedMettle1, extra=0x0
}

class Fractures(BossModule module) : Components.DirectionalParry(module, [(uint)OID.CrackedMettle1, (uint)OID.CrackedMettle2])
{
public override void OnStatusGain(Actor actor, ActorStatus status)
{
if ((OID)actor.OID is not OID.CrackedMettle1 and not OID.CrackedMettle2)
return;
var sides = (SID)status.ID switch
{
SID.RightwardFracture => Side.Front | Side.Back | Side.Left,
SID.LeftwardFracture => Side.Right | Side.Back | Side.Front,
SID.BackwardFracture => Side.Front | Side.Left | Side.Right,
_ => Side.None
};
if (sides != Side.None)
PredictParrySide(actor.InstanceID, sides);
}

public override void OnStatusLose(Actor actor, ActorStatus status)
{
if ((SID)status.ID is SID.RightwardFracture or SID.LeftwardFracture or SID.BackwardFracture)
UpdateState(actor.InstanceID, 0);
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (ActorStates.Count > 0)
{
var first = ActorStates.FirstOrDefault();
var target = WorldState.Actors.Find(first.Key)!;
var sideR = (uint)(Side.Front | Side.Back | Side.Left) << 4;
var sideL = (uint)(Side.Right | Side.Back | Side.Front) << 4;
var dir = first.Value == sideR ? target.Rotation - 90.Degrees() : first.Value == sideL ? target.Rotation + 90.Degrees() : target.Rotation + 180.Degrees();
hints.AddForbiddenZone(ShapeDistance.InvertedCone(target.Position, 20, dir, 20.Degrees()));
}
}
}

class SteelhogsRevenge(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SteelhogsRevenge), new AOEShapeCircle(12));
class RuthlessBombardment1(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.RuthlessBombardment1), new AOEShapeCircle(8));

abstract class Bombardment(BossModule module, AID aid) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCircle(4));
class RuthlessBombardment2(BossModule module) : Bombardment(module, AID.RuthlessBombardment2);
class AreaBombardment(BossModule module) : Bombardment(module, AID.AreaBombardment);

class RagingArtillery(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.RagingArtilleryFirst));
class MagitekCannon(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.MagitekCannon), 6);
class MagitekMissile(BossModule module) : Components.PersistentVoidzone(module, 3, m => m.Enemies(OID.MagitekMissile).Where(x => !x.IsDead), 5);

class NeedleGunOilShower(BossModule module) : Components.GenericAOEs(module)
{
private readonly List<AOEInstance> _aoes = [];
private static readonly AOEShapeCone cone1 = new(40, 135.Degrees());
private static readonly AOEShapeCone cone2 = new(40, 45.Degrees());

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
if (_aoes.Count > 0)
yield return _aoes[0] with { Color = Colors.Danger };
if (_aoes.Count > 1)
yield return _aoes[1] with { Risky = false };
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.OilShower1 or AID.OilShower2)
_aoes.Add(new(cone1, caster.Position, spell.Rotation, Module.CastFinishAt(spell)));
else if ((AID)spell.Action.ID is AID.NeedleGun1 or AID.NeedleGun2)
_aoes.Add(new(cone2, caster.Position, spell.Rotation, Module.CastFinishAt(spell)));
if (_aoes.Count > 0)
_aoes.SortBy(x => x.Activation);
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (_aoes.Count > 0 && (AID)spell.Action.ID is AID.OilShower1 or AID.OilShower2 or AID.NeedleGun1 or AID.NeedleGun2)
_aoes.RemoveAt(0);
}
}

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

private static readonly AOEShapeCircle circle = new(14);

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

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID is AID.HeavySurfaceMissiles1 or AID.HeavySurfaceMissiles2 or AID.HeavySurfaceMissiles3 or AID.HeavySurfaceMissiles4)
_aoes.Add(new(circle, caster.Position, spell.Rotation, Module.CastFinishAt(spell)));
if (_aoes.Count > 0)
_aoes.SortBy(x => x.Activation);
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (_aoes.Count > 0 && (AID)spell.Action.ID is AID.HeavySurfaceMissiles1 or AID.HeavySurfaceMissiles2 or AID.HeavySurfaceMissiles3 or AID.HeavySurfaceMissiles4)
_aoes.RemoveAt(0);
}
}

class HomingLaser(BossModule module) : Components.GenericBaitAway(module)
{
private static readonly AOEShapeRect rect = new(42, 4);

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.HomingLaserMarker)
CurrentBaits.Add(new(caster, WorldState.Actors.Find(spell.TargetID)!, rect, WorldState.FutureTime(5.7f)));
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID == AID.HomingLaser)
CurrentBaits.Clear();
}
}

class TheMightiestShieldStates : StateMachineBuilder
{
public TheMightiestShieldStates(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<Fractures>()
.ActivateOnEnter<RuthlessBombardment1>()
.ActivateOnEnter<RuthlessBombardment2>()
.ActivateOnEnter<AreaBombardment>()
.ActivateOnEnter<RagingArtillery>()
.ActivateOnEnter<MagitekCannon>()
.ActivateOnEnter<NeedleGunOilShower>()
.ActivateOnEnter<HeavySurfaceMissiles>()
.ActivateOnEnter<MagitekMissile>()
.ActivateOnEnter<SteelhogsRevenge>()
.ActivateOnEnter<HomingLaser>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.Quest, GroupID = 70377, NameID = 12923)]
public class TheMightiestShield(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena)
{
private static readonly ArenaBoundsComplex arena = new([new Polygon(new(-191, 72), 14.5f, 20)]);

protected override void DrawEnemies(int pcSlot, Actor pc)
{
Arena.Actors(Enemies(OID.UnyieldingMettle1).Concat([PrimaryActor]).Concat(Enemies(OID.UnyieldingMettle2)).Concat(Enemies(OID.UnyieldingMettle3))
.Concat(Enemies(OID.CrackedMettle1)).Concat(Enemies(OID.CrackedMettle2)).Concat(Enemies(OID.CravenFollower1)).Concat(Enemies(OID.CravenFollower2))
.Concat(Enemies(OID.CravenFollower3)).Concat(Enemies(OID.CravenFollower4)).Concat(Enemies(OID.CravenFollower5)).Concat(Enemies(OID.CravenFollower6)
.Concat(Enemies(OID.CravenFollower7))).Concat(Enemies(OID.CravenFollower8)).Concat(Enemies(OID.MagitekMissile)));
}

protected override bool CheckPull() => Raid.WithoutSlot().Any(x => x.InCombat);
}
2 changes: 1 addition & 1 deletion BossMod/Modules/Endwalker/Unreal/Un3Sophia/Demiurges.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace BossMod.Endwalker.Unreal.Un3Sophia;

// shows all three demiurges + handles directional parry from first; the reason is to simplify condition checks
class Demiurges(BossModule module) : Components.DirectionalParry(module, (uint)OID.Demiurge1)
class Demiurges(BossModule module) : Components.DirectionalParry(module, [(uint)OID.Demiurge1])
{
private readonly IReadOnlyList<Actor> _second = module.Enemies(OID.Demiurge2);
private readonly IReadOnlyList<Actor> _third = module.Enemies(OID.Demiurge3);
Expand Down
12 changes: 4 additions & 8 deletions BossMod/Modules/Heavensward/Extreme/Ext3Thordan/Ex3Thordan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,17 @@ class BurningChains(BossModule module) : Components.Chains(module, (uint)TetherI
class SerZephirin(BossModule module) : Components.Adds(module, (uint)OID.SerZephirin);

class BossReappear(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.BossReappear));
class LightOfAscalon(BossModule module) : Components.Knockback(module, ActionID.MakeSpell(AID.FaithUnmoving), true)
class LightOfAscalon(BossModule module) : Components.Knockback(module, ActionID.MakeSpell(AID.LightOfAscalon), true)
{
private readonly List<Source> _sources = [];

public override IEnumerable<Source> Sources(int slot, Actor actor) => _sources;

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{

if ((AID)spell.Action.ID == AID.LightOfAscalon)
{
++NumCasts;
if (_sources.Count > 0)
_sources.RemoveAt(0);
}
base.OnEventCast(caster, spell);
if (_sources.Count > 0 && (AID)spell.Action.ID == AID.LightOfAscalon)
_sources.RemoveAt(0);
else if ((AID)spell.Action.ID == AID.BossReappear)
{
for (var i = 0; i < 7; ++i)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace BossMod.Shadowbringers.Foray.DelubrumReginae.Normal.DRN3QueensGuard;

class CoatOfArms(BossModule module) : Components.DirectionalParry(module, (uint)OID.AetherialWard)
class CoatOfArms(BossModule module) : Components.DirectionalParry(module, [(uint)OID.AetherialWard])
{
public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace BossMod.Shadowbringers.Foray.DelubrumReginae.DRS4QueensGuard;

class CoatOfArms(BossModule module) : Components.DirectionalParry(module, (uint)OID.AetherialWard)
class CoatOfArms(BossModule module) : Components.DirectionalParry(module, [(uint)OID.AetherialWard])
{
public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace BossMod.Shadowbringers.Ultimate.TEA;

class P2PlasmaShield(BossModule module) : Components.DirectionalParry(module, (uint)OID.PlasmaShield)
class P2PlasmaShield(BossModule module) : Components.DirectionalParry(module, [(uint)OID.PlasmaShield])
{
public override void OnActorCreated(Actor actor)
{
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Replay/Visualization/OpList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class OpList(Replay replay, Replay.Encounter? enc, ModuleRegistry.Info? moduleIn
public static readonly HashSet<uint> BoringOIDs = [0x3E1A, 0x3E1B, 0x3E1C, 0x447E, 0x447D, 0x4480, 0x4583, 0x447F, 0x4584, 0x4570, 0x4256, 0x260E, 0x260B,
0x2630, 0x2611, 0x2610, 0x2617, 0x2608, 0x2613, 0x2618, 0x2609, 0x261A, 0x262F, 0x2609, 0x2614, 0x2664, 0x2668, 0x2619, 0x2631, 0x2632, 0x260A, 0x2616, 0x2667,
0x2E7F, 0x2F33, 0x2F32, 0x2F38, 0x2E80, 0x2E82, 0x2E81, 0x2F36, 0x2E7D, 0x2F35, 0x2EB0, 0x2F31, 0x2F37, 0x2E7C, 0x2E7B, 0x2EAE, 0x2F3A, 0x2F30, 0x2E7E, 0x2EAF,
0x428B ];
0x428B, 0x44B8, 0x43D2, 0x43D1 ];
private readonly HashSet<ActionID> _filteredActions = [];
private readonly HashSet<uint> _filteredStatuses = [];
private readonly HashSet<uint> _filteredDirectorUpdateTypes = [];
Expand Down

0 comments on commit 7eb09ce

Please sign in to comment.