Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/awgil/ffxiv_bossmod into wip
Browse files Browse the repository at this point in the history
  • Loading branch information
xanunderscore committed Jan 3, 2025
2 parents a44067a + 15e120a commit aa06e53
Show file tree
Hide file tree
Showing 26 changed files with 1,015 additions and 132 deletions.
4 changes: 4 additions & 0 deletions BossMod/Autorotation/PlanExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ private List<EntryData> BuildEntries(List<Plan.Entry> entries)
var windowStart = s.EnterTime + Math.Min(s.Duration, entry.TimeSinceActivation);
res.Add(new(windowStart, windowStart + entry.WindowLength, s.BranchID, s.NumBranches, entry.Value));
}
else
{
Service.Log($"Failed to find state {entry.StateID:X} for plan {Plan?.Guid}");
}
}
}
return res;
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Config/GroupAssignment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class GroupAssignment

public int this[PartyRolesConfig.Assignment r]
{
get => Assignments[(int)r];
get => (int)r is var index && index >= 0 && index < Assignments.Length ? Assignments[index] : -1;
set => Assignments[(int)r] = value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Break(BossModule module) : Components.GenericGaze(module)
{
public readonly List<Eye> Eyes = [];

public override IEnumerable<Eye> ActiveEyes(int slot, Actor actor) => Eyes;//_casters.Where(c => c.CastInfo?.TargetID != actor.InstanceID).Select(c => new Eye(EyePosition(c), Module.CastFinishAt(c.CastInfo), Range: range));
public override IEnumerable<Eye> ActiveEyes(int slot, Actor actor) => Eyes;

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ class Atomos(BossModule module) : Components.Adds(module, (uint)OID.Atomos);
class LoomingChaos(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.LoomingChaosAOE));

// TODO: tankswap hints component for phase1
// TODO: phase 2 squares, break timer, teleport zones
// TODO: particle concentration towers
// TODO: evil seed
[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1010, NameID = 13624)]
// TODO: phase 2 teleport zones?
// TODO: grim embrace / curse of darkness prevent turning
[ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1010, NameID = 13624)]
public class Ch01CloudOfDarkness(WorldState ws, Actor primary) : BossModule(ws, primary, DefaultCenter, InitialBounds)
{
public static readonly WPos DefaultCenter = new(100, 100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ public enum OID : uint
StygianTendrils = 0x4622, // R1.200, x0 (spawn during fight), evil seed
CloudletOfDarkness = 0x4623, // R3.000, x0 (spawn during fight), criss-cross source
BallOfNaught = 0x4624, // R1.500, x1, (en)death sphere
//_Gen_DreadGale = 0x4625, // R1.200, x1
//DreadGale = 0x4625, // R1.200, x1, ???
SinisterEye = 0x4626, // R2.800, x2, break gaze source
AtomosSpawnPoint = 0x1EBD7B, // R0.500, x0 (spawn during fight), EventObj type
EvilSeed = 0x1E9B3B, // R0.500, x0 (spawn during fight), EventObj type
//_Gen_Actor1e8536 = 0x1E8536, // R2.000, x1, EventObj type
//_Gen_Exit = 0x1E850B, // R0.500, x1, EventObj type
}

public enum AID : uint
Expand Down Expand Up @@ -140,28 +138,27 @@ public enum AID : uint
public enum SID : uint
{
//_Gen_ArcaneDesign = 4180, // Boss->Boss, extra=0x0
//_Gen_VeilOfDarkness = 4179, // Boss->Boss, extra=0x0
//_Gen_LightningResistanceDown = 4386, // Helper/Boss->player, extra=0x1/0x2/0x3/0x4/0x5/0x6/0x7/0x8/0x9/0xA/0xB/0xC/0xD/0xE/0xF/0x10
DeadlyEmbrace = 4181, // none->player, extra=0x0
AbyssalEdge = 4182, // Boss->Boss, extra=0x0 (endeath/enaero stored)
//_Gen_VeilOfDarkness = 4179, // Boss->Boss, extra=0x0
//_Gen_CloyingCondensation = 2532, // none->player, extra=0x0
//_Gen_CloyingCondensation = 2532, // none->player, extra=0x0, prevent jumps?
//_Gen_ = 4388, // none->StygianShadow, extra=0x1052
//_Gen_ = 4387, // none->Boss, extra=0x1051
InnerDarkness = 4177, // none->player, extra=0x0, on main platform
OuterDarkness = 4178, // none->player, extra=0x0, on side platform
//_Gen_Rehabilitation = 4191, // none->Boss, extra=0x1/0x4/0x3/0x2
//_Gen_LifeDrain = 1377, // none->player, extra=0x0
//_Gen_CraftersGrace = 45, // player->player, extra=0x50
CurseOfDarkness = 2387, // none->player, extra=0x0
//_Gen_StabWound = 3061, // none->player, extra=0x0
//_Gen_StabWound = 3062, // none->player, extra=0x0
//_Gen_ThornyVine = 445, // none->player, extra=0x0
//_Gen_ForwardWithThee = 2240, // none->player, extra=0x33F
//_Gen_Stun = 149, // none->player, extra=0x0
//_Gen_BackWithThee = 2241, // none->player, extra=0x340
//_Gen_LeftWithThee = 2242, // none->player, extra=0x341
//_Gen_Stun = 2656, // none->player, extra=0x0
//_Gen_RightWithThee = 2243, // none->player, extra=0x342
//_Gen_Stun = 149, // none->player, extra=0x0
//_Gen_Stun = 2656, // none->player, extra=0x0
}

public enum IconID : uint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,18 @@ private void Subphase1Variant2End(uint id, float delay)
private void Subphase2(uint id, float delay)
{
DelugeOfDarkness2(id, delay);
DarkDominion(id + 0x10000, 9.3f);
ThirdArtOfDarknessParticleConcentration(id + 0x20000, 4);
DarkDominion(id + 0x10000, 9.3f); // note: 1s after cast ends, outer ring becomes dangerous
ThirdArtOfDarknessParticleConcentration(id + 0x20000, 4); // note: 3s after towers resolve, outer ring becomes normal
GhastlyGloom(id + 0x30000, 12.3f);
CurseOfDarkness(id + 0x40000, 8.3f);
EvilSeed(id + 0x50000, 9.9f);
ChaosCondensedDiffusiveForceParticleBeam(id + 0x60000, 8.1f);
EvilSeedChaosCondensedDiffusiveForceParticleBeam(id + 0x50000, 9.9f);
ActivePivotParticleBeam(id + 0x70000, 4.4f);
LoomingChaos(id + 0x80000, 6.2f);

CurseOfDarkness(id + 0x100000, 11.9f);
ParticleConcentrationPhaser(id + 0x110000, 4.2f);
DarkDominion(id + 0x120000, 1);
FeintParticleBeamThirdActOfDarkness(id + 0x130000, 3.1f);
DarkDominion(id + 0x120000, 1); // note: 1s after cast ends, outer ring becomes dangerous
FeintParticleBeamThirdActOfDarkness(id + 0x130000, 3.1f); // note: 2.5s after act of darkness resolves, outer ring becomes normal
GhastlyGloom(id + 0x140000, 11.4f);
PhaserChaosCondensedDiffusiveForceParticleBeam(id + 0x150000, 3.4f);
FloodOfDarknessAdds(id + 0x160000, 3);
Expand Down Expand Up @@ -223,6 +222,8 @@ private void DelugeOfDarkness2(uint id, float delay)
ComponentCondition<StygianShadow>(id + 0x10, 4.2f, comp => comp.ActiveActors.Any(), "Platform adds")
.ActivateOnEnter<StygianShadow>()
.ActivateOnEnter<Atomos>()
.ActivateOnEnter<Phase2OuterRing>()
.ActivateOnEnter<Phase2InnerCells>()
.ActivateOnEnter<DarkEnergyParticleBeam>(); // overlaps with multiple mechanics
}

Expand All @@ -246,7 +247,8 @@ private void ThirdArtOfDarknessParticleConcentration(uint id, float delay)
.DeactivateOnExit<ThirdArtOfDarknessHyperFocusedParticleBeam>()
.DeactivateOnExit<ThirdArtOfDarknessMultiProngedParticleBeam>()
.DeactivateOnExit<ThirdArtOfDarknessCleave>();
ComponentCondition<ParticleConcentration>(id + 0x50, 3.6f, comp => comp.NumCasts > 0, "Towers")
ComponentCondition<ParticleConcentration>(id + 0x50, 3.6f, comp => comp.Towers.Count == 0, "Towers")
.ExecOnEnter<ParticleConcentration>(comp => comp.ShowOuterTowers())
.DeactivateOnExit<ParticleConcentration>();
}

Expand Down Expand Up @@ -276,29 +278,29 @@ private State FloodOfDarknessAdds(uint id, float delay)
.DeactivateOnExit<FloodOfDarknessAdd>();
}

// TODO: this one needs a lot of thought
private void EvilSeed(uint id, float delay)
private void EvilSeedChaosCondensedDiffusiveForceParticleBeam(uint id, float delay)
{
ComponentCondition<EvilSeedBait>(id, delay, comp => comp.Baiters.Any())
.ActivateOnEnter<EvilSeedBait>();
ComponentCondition<EvilSeedAOE>(id + 0x10, 8.1f, comp => comp.Casters.Count > 0, "Seed plant")
.ActivateOnEnter<EvilSeedAOE>()
.ActivateOnEnter<EvilSeedVoidzone>()
.DeactivateOnExit<EvilSeedBait>();

GhastlyGloom(id + 0x1000, 2.8f)
.DeactivateOnExit<EvilSeedAOE>();

ComponentCondition<ThornyVine>(id + 0x2000, 14, comp => comp.Targets.Any())
.ActivateOnEnter<ThornyVine>();
ComponentCondition<ThornyVine>(id + 0x2010, 3, comp => comp.HaveTethers);
FloodOfDarknessAdds(id + 0x2020, 2.2f)
.DeactivateOnExit<ThornyVine>();
}
ComponentCondition<ThornyVine>(id + 0x2010, 3, comp => comp.TethersAssigned, "Tethers");
FloodOfDarknessAdds(id + 0x2020, 2.2f);

private void ChaosCondensedDiffusiveForceParticleBeam(uint id, float delay)
{
CastMulti(id, [AID.ChaosCondensedParticleBeam, AID.DiffusiveForceParticleBeam], delay, 8)
CastMulti(id + 0x3000, [AID.ChaosCondensedParticleBeam, AID.DiffusiveForceParticleBeam], 8.1f, 8)
.ActivateOnEnter<ChaosCondensedParticleBeam>()
.ActivateOnEnter<DiffusiveForceParticleBeam>();
Condition(id + 0x10, 0.7f, () => Module.FindComponent<ChaosCondensedParticleBeam>()?.NumCasts > 0 || Module.FindComponent<DiffusiveForceParticleBeam>()?.Spreads.Count == 0, "Spread/line stacks")
.ActivateOnEnter<DiffusiveForceParticleBeam>()
.DeactivateOnExit<EvilSeedVoidzone>()
.DeactivateOnExit<ThornyVine>();
Condition(id + 0x3010, 0.7f, () => Module.FindComponent<ChaosCondensedParticleBeam>()?.NumCasts > 0 || Module.FindComponent<DiffusiveForceParticleBeam>()?.Spreads.Count == 0, "Spread/line stacks")
.DeactivateOnExit<ChaosCondensedParticleBeam>()
.DeactivateOnExit<DiffusiveForceParticleBeam>(); // TODO: show second wave ...
}
Expand Down Expand Up @@ -341,7 +343,8 @@ private void ParticleConcentrationPhaser(uint id, float delay)
.ActivateOnEnter<ParticleConcentration>(); // TODO: towers appear 1s after cast end
ComponentCondition<Phaser>(id + 0x11, 1.5f, comp => comp.NumCasts >= 6, "Adds sides/front")
.DeactivateOnExit<Phaser>();
ComponentCondition<ParticleConcentration>(id + 0x20, 6.6f, comp => comp.NumCasts > 0, "Towers")
ComponentCondition<ParticleConcentration>(id + 0x20, 6.6f, comp => comp.Towers.Count == 0, "Towers")
.ExecOnEnter<ParticleConcentration>(comp => comp.ShowOuterTowers())
.DeactivateOnExit<ParticleConcentration>();
}

Expand Down Expand Up @@ -386,6 +389,8 @@ private void FloodOfDarkness2(uint id, float delay)
CastStart(id, AID.FloodOfDarkness2, delay, "Adds disappear")
.DeactivateOnExit<StygianShadow>()
.DeactivateOnExit<Atomos>()
.DeactivateOnExit<Phase2OuterRing>()
.DeactivateOnExit<Phase2InnerCells>()
.DeactivateOnExit<DarkEnergyParticleBeam>();
CastEnd(id + 1, 7, "Raidwide + arena transition")
.OnExit(() => Module.Arena.Bounds = Ch01CloudOfDarkness.InitialBounds)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
namespace BossMod.Dawntrail.Chaotic.Ch01CloudOfDarkness;

// envcontrols:
// 00 = main bounds telegraph
// - 00200010 - phase 1
// - 00020001 - phase 2
// - 00040004 - remove telegraph (note that actual bounds are controlled by something else!)
class DelugeOfDarkness1(BossModule module) : Components.GenericAOEs(module)
{
private AOEInstance? _aoe;
Expand All @@ -15,30 +20,6 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell)
}
}

// envcontrols:
// 00 = main bounds telegraph
// - 00200010 - phase 1
// - 00020001 - phase 2
// - 00040004 - remove telegraph (note that actual bounds are controlled by something else!)
// 02 = outer ring
// - 00020001 - become dangerous
// - 00080004 - restore to normal
// 03-1E = mid squares
// - 08000001 - init
// - 00200010 - become occupied
// - 02000001 - become free
// - 00800040 - player is standing for too long, will break soon
// - 00080004 - break
// - 00020001 - repair
// - arrangement:
// 04 0B
// 03 05 06 07 0E 0D 0C 0A
// 08 0F
// 09 10
// 17 1E
// 16 1D
// 11 13 14 15 1C 1B 1A 18
// 12 19
class DelugeOfDarkness2(BossModule module) : Components.GenericAOEs(module)
{
private AOEInstance? _aoe;
Expand All @@ -53,3 +34,117 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell)
_aoe = new(_shape, Module.Center, default, Module.CastFinishAt(spell));
}
}

class Phase2OuterRing(BossModule module) : Components.GenericAOEs(module)
{
public bool Dangerous;
private AOEInstance? _aoe;

private static readonly AOEShapeDonut _shape = new(34, 40);

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe);

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.DarkDominion)
_aoe = new(_shape, Module.Center, default, Module.CastFinishAt(spell, 1.1f));
}

public override void OnEventEnvControl(byte index, uint state)
{
if (index != 2)
return;
switch (state)
{
case 0x00020001:
Dangerous = true;
break;
case 0x00080004:
Dangerous = false;
_aoe = null;
break;
default:
ReportError($"Unexpected envcontrol {state:X8}");
break;
}
}
}

class Phase2InnerCells(BossModule module) : BossComponent(module)
{
private readonly DateTime[] _breakTime = new DateTime[28];

public override void AddHints(int slot, Actor actor, TextHints hints)
{
var cell = CellIndex(actor.Position - Module.Center) - 3;
var breakTime = cell >= 0 && cell < _breakTime.Length ? _breakTime[cell] : default;
if (breakTime != default)
{
var remaining = Math.Max(0, (breakTime - WorldState.CurrentTime).TotalSeconds);
hints.Add($"Cell breaks in {remaining:f1}s", remaining < 10);
}
}

public override void OnEventEnvControl(byte index, uint state)
{
// 03-1E = mid squares
// - 08000001 - init
// - 00200010 - become occupied
// - 02000001 - become free
// - 00800040 - player is standing for too long (38s), will break soon (in 6s)
// - 00080004 - break
// - 00020001 - repair
// - arrangement:
// 04 0B
// 03 05 06 07 0E 0D 0C 0A
// 08 0F
// 09 10
// 17 1E
// 16 1D
// 11 13 14 15 1C 1B 1A 18
// 12 19
if (index is < 3 or > 30)
return;
_breakTime[index - 3] = state switch
{
0x00200010 => WorldState.FutureTime(44),
0x00800040 => WorldState.FutureTime(6),
_ => default,
};
}

private int CoordinateToCell(float c) => (int)Math.Floor(c / 6);
private int CellIndex(WDir offset) => CellIndex(CoordinateToCell(offset.X), CoordinateToCell(offset.Z));
private int CellIndex(int x, int y) => (x, y) switch
{
(-4, -3) => 3,
(-3, -4) => 4,
(-3, -3) => 5,
(-2, -3) => 6,
(-1, -3) => 7,
(-3, -2) => 8,
(-3, -1) => 9,
(+3, -3) => 10,
(+2, -4) => 11,
(+2, -3) => 12,
(+1, -3) => 13,
(+0, -3) => 14,
(+2, -2) => 15,
(+2, -1) => 16,
(-4, +2) => 17,
(-3, +3) => 18,
(-3, +2) => 19,
(-2, +2) => 20,
(-1, +2) => 21,
(-3, +1) => 22,
(-3, +0) => 23,
(+3, +2) => 24,
(+2, +3) => 25,
(+2, +2) => 26,
(+1, +2) => 27,
(+0, +2) => 28,
(+2, +1) => 29,
(+2, +0) => 30,
_ => -1
};
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
namespace BossMod.Dawntrail.Chaotic.Ch01CloudOfDarkness;

// TODO: who gets radius 7 and who gets radius 5?
// TODO: show for second wave too...
// note: it seems that normally first wave (radius 7) hits people inside, and second wave (radius 5) hits people outside
// however, if some of the players in the mid are dead, some players on the outside will be hit by first wave (up to a total of 12 hits)
// if there are <= 12 players alive, everyone will be hit by the first wave, and the second wave will never happen
// so for safety we just show larger radius around everyone
// TODO: show second wave for players not hit by first wave
class DiffusiveForceParticleBeam(BossModule module) : Components.UniformStackSpread(module, 0, 7)
{
public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.DiffusiveForceParticleBeam)
AddSpreads(Raid.WithoutSlot(), Module.CastFinishAt(spell, 0.7f));
AddSpreads(Raid.WithoutSlot(true), Module.CastFinishAt(spell, 0.7f));
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,15 @@ public override void OnEventIcon(Actor actor, uint iconID, ulong targetID)

class EvilSeedAOE(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.EvilSeedAOE), 5);

// todo: should be chains...
class ThornyVine(BossModule module) : BossComponent(module)
class EvilSeedVoidzone(BossModule module) : Components.PersistentVoidzone(module, 5, module => module.Enemies(OID.EvilSeed).Where(z => z.EventState != 7));

class ThornyVine(BossModule module) : Components.Chains(module, (uint)TetherID.ThornyVine, default, 25)
{
public BitMask Targets;
public bool HaveTethers;

public override void OnEventIcon(Actor actor, uint iconID, ulong targetID)
{
if (iconID == (uint)IconID.ThornyVineBait)
Targets.Set(Raid.FindSlot(actor.InstanceID));
}

public override void OnTethered(Actor source, ActorTetherInfo tether)
{
if (tether.ID == (uint)TetherID.ThornyVine)
HaveTethers = true;
}
}
Loading

0 comments on commit aa06e53

Please sign in to comment.