Skip to content

Commit b492365

Browse files
Merge pull request #948 from FFXIV-CombatReborn/mergeWIP
Kam'lanaut module
2 parents 6a25de8 + 3b2a380 commit b492365

File tree

11 files changed

+424
-9
lines changed

11 files changed

+424
-9
lines changed

BossMod/BossModule/BossModuleManager.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,15 @@ public void Update()
109109
if (isActive != wasActive)
110110
(isActive ? ModuleActivated : ModuleDeactivated).Fire(m);
111111

112+
var actor = m.PrimaryActor;
112113
// unload module because it is not active and player is out of desired range
113-
if (!isActive && (playerPos - m.PrimaryActor.PosRot.AsVector3()).LengthSquared() > maxSq && m.PrimaryActor.SpawnIndex != -99)
114+
if (!isActive && (playerPos - actor.PosRot.AsVector3()).LengthSquared() > maxSq && actor.SpawnIndex != -99)
114115
{
115116
UnloadModule(i--);
116-
PendingModules.Add(m);
117-
Service.Log($"[BMM] Boss module '{m.GetType()}' moved to pending for actor {m.PrimaryActor}");
117+
if (!actor.IsDestroyed)
118+
{
119+
ActorAdded(actor);
120+
}
118121
continue;
119122
}
120123

@@ -129,7 +132,6 @@ public void Update()
129132
if (isActive && m.CheckReset())
130133
{
131134
ModuleDeactivated.Fire(m);
132-
var actor = m.PrimaryActor;
133135
UnloadModule(i--);
134136
if (!actor.IsDestroyed)
135137
ActorAdded(actor);

BossMod/Components/Knockback.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ protected struct PlayerImmuneState
5656
public readonly bool ImmuneAt(DateTime time) => RoleBuffExpire > time || JobBuffExpire > time || DutyBuffExpire > time;
5757
}
5858

59-
public readonly bool StopAtWall = stopAtWall; // use if wall is solid rather than deadly
60-
public readonly bool StopAfterWall = stopAfterWall; // use if the wall is a polygon where you need to check for intersections
59+
public bool StopAtWall = stopAtWall; // use if wall is solid rather than deadly
60+
public bool StopAfterWall = stopAfterWall; // use if the wall is a polygon where you need to check for intersections
6161
public readonly int MaxCasts = maxCasts; // use to limit number of drawn knockbacks
6262
private const float approxHitBoxRadius = 0.499f; // calculated because due to floating point errors this does not result in 0.001
6363
private const float maxIntersectionError = 0.5f - approxHitBoxRadius; // calculated because due to floating point errors this does not result in 0.001

BossMod/Data/ActionID.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ public enum ActionType : byte
3232
public enum Positional { Any, Flank, Rear, Front }
3333

3434
// high byte is type, low 3 bytes is ID
35-
public readonly record struct ActionID(uint Raw)
35+
public readonly struct ActionID(uint raw)
3636
{
37+
public readonly uint Raw = raw;
3738
public readonly ActionType Type => (ActionType)(Raw >> 24);
38-
public readonly uint ID => Raw & 0x00FFFFFFu;
39+
public readonly uint ID = raw & 0x00FFFFFFu;
3940

4041
public ActionID(ActionType type, uint id) : this(((uint)type << 24) | id) { }
4142

BossMod/Data/Actor.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,17 @@ public sealed record class ActorCastInfo
4949
}
5050

5151
// note: 'main' target could be completely different and unrelated to actual affected targets
52-
public sealed record class ActorCastEvent(ActionID Action, ulong MainTargetID, float AnimationLockTime, uint MaxTargets, Vector3 TargetPos, uint GlobalSequence, uint SourceSequence, Angle Rotation)
52+
public sealed class ActorCastEvent(ActionID action, ulong mainTargetID, float animationLockTime, uint maxTargets, Vector3 targetPos, uint globalSequence, uint sourceSequence, Angle rotation)
5353
{
54+
public readonly ActionID Action = action;
55+
public readonly ulong MainTargetID = mainTargetID;
56+
public readonly float AnimationLockTime = animationLockTime;
57+
public readonly uint MaxTargets = maxTargets;
58+
public readonly Vector3 TargetPos = targetPos;
59+
public readonly uint GlobalSequence = globalSequence;
60+
public readonly uint SourceSequence = sourceSequence;
61+
public readonly Angle Rotation = rotation;
62+
5463
public readonly struct Target(ulong id, ActionEffects effects)
5564
{
5665
public readonly ulong ID = id;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
namespace BossMod.Dawntrail.Alliance.A23Kamlanaut;
2+
3+
sealed class ElementalBladeWide(BossModule module) : Components.SimpleAOEGroups(module, [(uint)AID.IceBladeWide, (uint)AID.LightningBladeWide, (uint)AID.FireBladeWide,
4+
(uint)AID.EarthBladeWide, (uint)AID.WaterBladeWide, (uint)AID.WindBladeWide], new AOEShapeRect(80f, 10f), riskyWithSecondsLeft: 3d);
5+
sealed class ElementalBladeNarrow(BossModule module) : Components.SimpleAOEGroups(module, [(uint)AID.IceBladeNarrow, (uint)AID.LightningBladeNarrow, (uint)AID.FireBladeNarrow,
6+
(uint)AID.EarthBladeNarrow, (uint)AID.WaterBladeNarrow, (uint)AID.WindBladeNarrow], new AOEShapeRect(80f, 2.5f), riskyWithSecondsLeft: 3d);
7+
sealed class ElementalResonance(BossModule module) : Components.SimpleAOEs(module, (uint)AID.ElementalResonance, 18f);
8+
sealed class SublimeElementsWide(BossModule module) : Components.SimpleAOEGroups(module, [(uint)AID.SublimeIceWide, (uint)AID.SublimeLightningWide, (uint)AID.SublimeFireWide,
9+
(uint)AID.SublimeEarthWide, (uint)AID.SublimeWaterWide, (uint)AID.SublimeWindWide], new AOEShapeCone(40f, 50f.Degrees()), riskyWithSecondsLeft: 3d);
10+
sealed class SublimeElementsNarrow(BossModule module) : Components.SimpleAOEGroups(module, [(uint)AID.SublimeIceNarrow, (uint)AID.SublimeLightningNarrow, (uint)AID.SublimeFireNarrow,
11+
(uint)AID.SublimeEarthNarrow, (uint)AID.SublimeWaterNarrow, (uint)AID.SublimeWindNarrow], new AOEShapeCone(40f, 10f.Degrees()), riskyWithSecondsLeft: 3d);
12+
sealed class EmpyrealBanishIV(BossModule module) : Components.StackWithCastTargets(module, (uint)AID.EmpyrealBanishIV, 5f, PartyState.MaxAllianceSize, PartyState.MaxAllianceSize);
13+
sealed class EmpyrealBanishIII(BossModule module) : Components.SpreadFromCastTargets(module, (uint)AID.EmpyrealBanishIII, 5f);
14+
sealed class GreatWheelCircle(BossModule module) : Components.SimpleAOEGroups(module, [(uint)AID.GreatWheelCircle1, (uint)AID.GreatWheelCircle2,
15+
(uint)AID.GreatWheelCircle3, (uint)AID.GreatWheelCircle4], 10f);
16+
sealed class GreatWheelCone(BossModule module) : Components.SimpleAOEs(module, (uint)AID.GreatWheelCone, new AOEShapeCone(80f, 90f.Degrees()));
17+
sealed class LightBladeIllumedEstoc(BossModule module) : Components.SimpleAOEGroups(module, [(uint)AID.IllumedEstoc, (uint)AID.LightBlade], new AOEShapeRect(120f, 6.5f), riskyWithSecondsLeft: 3.5d)
18+
{
19+
public override ReadOnlySpan<AOEInstance> ActiveAOEs(int slot, Actor actor)
20+
{
21+
var count = Casters.Count;
22+
if (count == 0)
23+
return [];
24+
25+
if (count != 2)
26+
{
27+
return base.ActiveAOEs(slot, actor);
28+
}
29+
return CollectionsMarshal.AsSpan(Casters);
30+
}
31+
}
32+
33+
sealed class TranscendentUnion(BossModule module) : Components.RaidwideCastDelay(module, (uint)AID.TranscendentUnionVisual, (uint)AID.TranscendentUnion, 6.6d, "Raidwide x7");
34+
sealed class EnspiritedSwordplayShockwave(BossModule module) : Components.RaidwideCasts(module, [(uint)AID.EnspiritedSwordplay, (uint)AID.Shockwave]);
35+
36+
[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", PrimaryActorOID = (uint)OID.Kamlanaut, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1058u, NameID = 14043u, Category = BossModuleInfo.Category.Alliance, Expansion = BossModuleInfo.Expansion.Dawntrail, SortOrder = 3)]
37+
public sealed class A23Kamlanaut(WorldState ws, Actor primary) : BossModule(ws, primary, ArenaCenter, P1Arena)
38+
{
39+
public static readonly WPos ArenaCenter = new(-200f, 150f);
40+
private static readonly Polygon[] p1Circle = [new(ArenaCenter, 29.5f, 128)]; // arena circle actually got 512 vertices, but 128 is a good enough approximation for this use case
41+
private static readonly Polygon[] p2Circle = [new(ArenaCenter, 20f, 128)];
42+
private static readonly Polygon[] voidzone = [new(ArenaCenter, 5f, 64)];
43+
public static readonly ArenaBoundsComplex P1Arena = new(p1Circle);
44+
public static readonly ArenaBoundsComplex P2Arena = new(p2Circle);
45+
public static readonly ArenaBoundsComplex P1ArenaDonut = new(p1Circle, voidzone);
46+
public static readonly ArenaBoundsComplex P2ArenaDonut = new(p2Circle, voidzone);
47+
private static readonly Rectangle[] bridges = GenerateBridges();
48+
private static Rectangle[] GenerateBridges()
49+
{
50+
var northCenter = new WDir(default, -20f);
51+
var rects = new Rectangle[3];
52+
var a120 = 120f.Degrees();
53+
for (var i = 0; i < 3; ++i)
54+
{
55+
var angle = a120 * i;
56+
rects[i] = new(ArenaCenter + northCenter.Rotate(angle), 5f, 20f, angle);
57+
}
58+
return rects;
59+
}
60+
61+
private static readonly Shape[] p2ArenaShapes = [.. p2Circle, .. bridges];
62+
public static readonly ArenaBoundsComplex P2ArenaWithBridges = new(p2ArenaShapes, ScaleFactor: 1.15f);
63+
public static readonly AOEShapeCustom P1p2transition = new(p1Circle, p2ArenaShapes);
64+
public static readonly ArenaBoundsComplex P2ArenaWithBridgesDonut = new(p2ArenaShapes, voidzone, ScaleFactor: 1.15f);
65+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
namespace BossMod.Dawntrail.Alliance.A23Kamlanaut;
2+
3+
public enum OID : uint
4+
{
5+
Kamlanaut = 0x48F8, // R6.0
6+
KamlanautClone = 0x48F9, // R6.0
7+
SublimeEstoc = 0x48FA, // R1.0
8+
ProvingGroundVoidzone = 0x1EBEEF, // R0.5
9+
Crystal1 = 0x1EBE87, // R0.5
10+
Crystal2 = 0x1EBE88, // R0.5
11+
Crystal3 = 0x1EBE85, // R0.5
12+
Crystal4 = 0x1EBE86, // R0.5
13+
Crystal5 = 0x1EBE84, // R0.5
14+
Crystal6 = 0x1EBE89, // R0.5
15+
Helper2 = 0x49C7, // R1.0
16+
Helper = 0x233C
17+
}
18+
19+
public enum AID : uint
20+
{
21+
AutoAttack = 44876, // Kamlanaut->player, no cast, single-target
22+
Teleport = 44225, // Kamlanaut->location, no cast, single-target
23+
24+
EnspiritedSwordplay = 44221, // Kamlanaut->self, 5.0s cast, range 60 circle
25+
26+
ProvingGround = 45065, // Kamlanaut->self, 3.0s cast, range 5 circle
27+
28+
SublimeElements = 45066, // Kamlanaut->self, 8.0+1,0s cast, single-target
29+
SublimeEarthWide = 44186, // Helper->self, 9.0s cast, range 40 100-degree cone
30+
SublimeEarthNarrow = 44180, // Helper->self, 9.0s cast, range 40 20-degree cone
31+
SublimeFireWide = 44185, // Helper->self, 9.0s cast, range 40 100-degree cone
32+
SublimeFireNarrow = 44179, // Helper->self, 9.0s cast, range 40 20-degree cone
33+
SublimeWindWide = 44190, // Helper->self, 9.0s cast, range 40 100-degree cone
34+
SublimeWindNarrow = 44184, // Helper->self, 9.0s cast, range 40 20-degree cone
35+
SublimeWaterWide = 44187, // Helper->self, 9.0s cast, range 40 100-degree cone
36+
SublimeWaterNarrow = 44181, // Helper->self, 9.0s cast, range 40 20-degree cone
37+
SublimeLightningWide = 44189, // Helper->self, 9.0s cast, range 40 100-degree cone
38+
SublimeLightningNarrow = 44183, // Helper->self, 9.0s cast, range 40 20-degree cone
39+
SublimeIceWide = 44188, // Helper->self, 9.0s cast, range 40 100-degree cone
40+
SublimeIceNarrow = 44182, // Helper->self, 9.0s cast, range 40 20-degree cone
41+
42+
ElementalBlade1 = 44177, // Kamlanaut->self, 8.0s cast, single-target
43+
ElementalBlade2 = 44178, // Kamlanaut->self, 11.0s cast, single-target
44+
IceBladeWide = 44200, // Helper->self, 9.0s cast, range 80 width 20 rect
45+
IceBladeNarrow = 44194, // Helper->self, 9.0s cast, range 80 width 5 rect
46+
LightningBladeWide = 44201, // Helper->self, 9.0s cast, range 80 width 20 rect
47+
LightningBladeNarrow = 44195, // Helper->self, 9.0s cast, range 80 width 5 rect
48+
FireBladeWide = 44197, // Helper->self, 9.0s cast, range 80 width 20 rect
49+
FireBladeNarrow = 44191, // Helper->self, 9.0s cast, range 80 width 5 rect
50+
EarthBladeWide = 44198, // Helper->self, 9.0s cast, range 80 width 20 rect
51+
EarthBladeNarrow = 44192, // Helper->self, 9.0s cast, range 80 width 5 rect
52+
WaterBladeWide = 44199, // Helper->self, 9.0s cast, range 80 width 20 rect
53+
WaterBladeNarrow = 44193, // Helper->self, 9.0s cast, range 80 width 5 rect
54+
WindBladeWide = 44202, // Helper->self, 9.0s cast, range 80 width 20 rect
55+
WindBladeNarrow = 44196, // Helper->self, 9.0s cast, range 80 width 5 rect
56+
57+
PrincelyBlowVisual = 44219, // Kamlanaut->self, 7.2+0,8s cast, single-target
58+
PrincelyBlow = 44220, // Helper->self, no cast, range 60 width 10 rect, knockback 30, dir forward
59+
60+
SublimeEstoc = 44204, // SublimeEstoc->self, 3.0s cast, range 40 width 5 rect
61+
GreatWheelCircle1 = 44205, // Kamlanaut->self, 3.0s cast, range 10 circle
62+
GreatWheelCircle2 = 44206, // Kamlanaut->self, 3.0s cast, range 10 circle
63+
GreatWheelCircle3 = 44207, // Kamlanaut->self, 3.0s cast, range 10 circle
64+
GreatWheelCircle4 = 44208, // Kamlanaut->self, 3.0s cast, range 10 circle
65+
GreatWheelCone = 44209, // Helper->self, 5.8s cast, range 80 180-degree cone
66+
67+
EsotericScrivening = 44210, // Kamlanaut->self, 6.0s cast, single-target
68+
ShockwaveVisual1 = 44409, // Kamlanaut->self, no cast, single-target
69+
ShockwaveVisual2 = 44410, // Kamlanaut->self, no cast, single-target
70+
ShockwaveVisual3 = 41727, // Helper->Kamlanaut, no cast, single-target
71+
Shockwave = 44211, // Helper->self, 5.2s cast, range 60 circle, raidwide
72+
73+
TranscendentUnionVisual = 44212, // Kamlanaut->self, 5.0s cast, single-target, raidwide x7
74+
ElementalEdge = 44289, // Helper->self, no cast, range 60 circle
75+
TranscendentUnion = 44213, // Helper->self, no cast, range 60 circle, big final raidwide
76+
77+
EsotericPalisade = 44214, // Kamlanaut->self, 3.0s cast, single-target
78+
CrystallineResonance = 44215, // Kamlanaut->self, 3.0s cast, single-target
79+
ElementalResonance = 44216, // Helper->self, 7.0s cast, range 18 circle
80+
81+
EmpyrealBanishIVVisual = 44907, // Kamlanaut->self, 4.0+1,0s cast, single-target
82+
EmpyrealBanishIV = 44224, // Helper->players, 5.0s cast, range 5 circle, stack
83+
EmpyrealBanishIII = 44223, // Helper->players, 5.0s cast, range 5 circle, spread
84+
85+
IllumedFacet = 44217, // Kamlanaut->self, 3.0s cast, single-target
86+
IllumedEstoc = 44218, // KamlanautClone->self, 8.0s cast, range 120 width 13 rect
87+
LightBlade = 44203, // Kamlanaut->self, 3.0s cast, range 120 width 13 rect
88+
ShieldBash = 44222 // Kamlanaut->self, 7.0s cast, range 60 circle, knockback 30, away from source
89+
}
90+
91+
public enum IconID : uint
92+
{
93+
PrincelyBlow = 613 // Kamlanaut->player
94+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace BossMod.Dawntrail.Alliance.A23Kamlanaut;
2+
3+
sealed class A23KamlanautStates : StateMachineBuilder
4+
{
5+
public A23KamlanautStates(BossModule module) : base(module)
6+
{
7+
TrivialPhase()
8+
.ActivateOnEnter<ArenaChanges>()
9+
.ActivateOnEnter<ElementalBladeWide>()
10+
.ActivateOnEnter<ElementalBladeNarrow>()
11+
.ActivateOnEnter<ElementalResonance>()
12+
.ActivateOnEnter<SublimeElementsWide>()
13+
.ActivateOnEnter<SublimeElementsNarrow>()
14+
.ActivateOnEnter<EmpyrealBanishIII>()
15+
.ActivateOnEnter<EmpyrealBanishIV>()
16+
.ActivateOnEnter<LightBladeIllumedEstoc>()
17+
.ActivateOnEnter<ShieldBash>()
18+
.ActivateOnEnter<SublimeEstoc>()
19+
.ActivateOnEnter<GreatWheelCircle>()
20+
.ActivateOnEnter<GreatWheelCone>()
21+
.ActivateOnEnter<TranscendentUnion>()
22+
.ActivateOnEnter<EnspiritedSwordplayShockwave>()
23+
.ActivateOnEnter<PrincelyBlow>()
24+
.ActivateOnEnter<PrincelyBlowKB>();
25+
}
26+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
namespace BossMod.Dawntrail.Alliance.A23Kamlanaut;
2+
3+
sealed class ArenaChanges(BossModule module) : Components.GenericAOEs(module)
4+
{
5+
private AOEInstance[] _aoe = [];
6+
private static readonly AOEShapeDonut donut = new(20f, 40f);
7+
private static readonly AOEShapeCircle circle = new(5);
8+
9+
public override ReadOnlySpan<AOEInstance> ActiveAOEs(int slot, Actor actor) => _aoe;
10+
11+
public override void OnCastStarted(Actor caster, ActorCastInfo spell)
12+
{
13+
if (spell.Action.ID == (uint)AID.ProvingGround)
14+
{
15+
_aoe = [new(circle, spell.LocXZ, default, Module.CastFinishAt(spell))];
16+
}
17+
}
18+
19+
public override void OnActorCreated(Actor actor)
20+
{
21+
if (actor.OID == (uint)OID.ProvingGroundVoidzone)
22+
{
23+
_aoe = [];
24+
var radius = Arena.Bounds.Radius;
25+
Arena.Bounds = radius switch
26+
{
27+
29.5f => A23Kamlanaut.P1ArenaDonut,
28+
20f => A23Kamlanaut.P2ArenaDonut,
29+
_ => A23Kamlanaut.P2ArenaWithBridgesDonut,
30+
};
31+
}
32+
}
33+
34+
public override void OnActorRenderflags(Actor actor, int renderflags)
35+
{
36+
if (renderflags == 256 && actor.OID == (uint)OID.ProvingGroundVoidzone)
37+
{
38+
var radius = Arena.Bounds.Radius;
39+
Arena.Bounds = radius switch
40+
{
41+
29.5f => A23Kamlanaut.P1Arena,
42+
20f => A23Kamlanaut.P2Arena,
43+
_ => A23Kamlanaut.P2ArenaWithBridges,
44+
};
45+
}
46+
}
47+
48+
public override void OnEventEnvControl(byte index, uint state)
49+
{
50+
switch (index)
51+
{
52+
case 0x00: // p1/p2 transition
53+
switch (state)
54+
{
55+
case 0x00020001u:
56+
_aoe = [new(A23Kamlanaut.P1p2transition, Arena.Center, default, WorldState.FutureTime(5.1d))];
57+
break;
58+
case 0x00200010u:
59+
SetArena(A23Kamlanaut.P2ArenaWithBridges);
60+
break;
61+
}
62+
break;
63+
case 0x63: // bridges
64+
switch (state)
65+
{
66+
case 0x00200010u:
67+
_aoe = [new(donut, A23Kamlanaut.ArenaCenter, default, WorldState.FutureTime(4.3d))];
68+
break;
69+
case 0x00020001u:
70+
SetArena(A23Kamlanaut.P2ArenaDonut);
71+
_aoe = [];
72+
break;
73+
case 0x00080004u:
74+
SetArena(A23Kamlanaut.P2ArenaWithBridges);
75+
break;
76+
}
77+
break;
78+
}
79+
void SetArena(ArenaBoundsComplex arena)
80+
{
81+
Arena.Bounds = arena;
82+
Arena.Center = arena.Center;
83+
_aoe = [];
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)