Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dawntrail dungeons 1&2 #379

Merged
merged 5 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions BossMod/BossModule/BossModuleInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum Expansion
Stormblood,
Shadowbringers,
Endwalker,
Dawntrail,
Global,

Count
Expand Down Expand Up @@ -75,6 +76,7 @@ public enum HuntRank : uint { B, A, S, SS }
Expansion.Stormblood => "SB",
Expansion.Shadowbringers => "ShB",
Expansion.Endwalker => "EW",
Expansion.Dawntrail => "DT",
_ => e.ToString()
};
}
Expand Down
4 changes: 2 additions & 2 deletions BossMod/Components/BaitAway.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether)
if (target == null)
return (null, null);

var (player, enemy) = source.Type == ActorType.Player ? (source, target) : (target, source);
if (player.Type != ActorType.Player || enemy.Type == ActorType.Player)
var (player, enemy) = source.Type is ActorType.Player or ActorType.DutySupport ? (source, target) : (target, source);
if (!(player.Type is ActorType.Player or ActorType.DutySupport) || enemy.Type == ActorType.Player)
{
ReportError($"Unexpected tether pair: {source.InstanceID:X} -> {target.InstanceID:X}");
return (null, null);
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Config/ModuleViewer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using BossMod.Autorotation;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
Expand Down Expand Up @@ -45,6 +44,7 @@ public ModuleViewer(PlanDatabase? planDB, WorldState ws)
Customize(BossModuleInfo.Expansion.Stormblood, 61877, exVersion?.GetRow(2)?.Name);
Customize(BossModuleInfo.Expansion.Shadowbringers, 61878, exVersion?.GetRow(3)?.Name);
Customize(BossModuleInfo.Expansion.Endwalker, 61879, exVersion?.GetRow(4)?.Name);
Customize(BossModuleInfo.Expansion.Dawntrail, 61880, exVersion?.GetRow(5)?.Name);

var contentType = Service.LuminaSheet<ContentType>();
Customize(BossModuleInfo.Category.Dungeon, contentType?.GetRow(2));
Expand Down
1 change: 1 addition & 0 deletions BossMod/Data/Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public enum ActorType : ushort
Pet = 0x202,
Chocobo = 0x203,
Enemy = 0x205,
DutySupport = 0x209,
EventNpc = 0x300,
Treasure = 0x400,
Aetheryte = 0x500,
Expand Down
189 changes: 189 additions & 0 deletions BossMod/Modules/Dawntrail/Dungeon/D01Ihuykatumu/D011PrimePunutiy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
namespace BossMod.Dawntrail.Dungeon.D01Ihuykatumu.D011PrimePunutiy;

public enum OID : uint
{
Helper = 0x233C, // R0.500, x16 (spawn during fight), 523 type
Boss = 0x4190, // R7.990, x1
ProdigiousPunutiy = 0x4191, // R4.230, x0 (spawn during fight)
Punutiy = 0x4192, // R2.820, x0 (spawn during fight)
PetitPunutiy = 0x4193, // R2.115, x0 (spawn during fight)
IhuykatumuFlytrap = 0x4194, // R1.600, x0 (spawn during fight)
}

public enum AID : uint
{
PunutiyPress = 36492, // Boss->self, 5.0s cast, range 60 circle
Hydrowave = 36493, // Boss->self, 4.0s cast, range 60 30-degree cone
AddsHydrowave = 36509,
Resurface = 36494, // Boss->self, 5.0s cast, range 100 60-degree cone
Resurface2 = 36495, // Boss->self, 7.0s cast, single-target
Bury1 = 36497, // 233C->self, 4.0s cast, range 12 circle
Bury2 = 36498, // 233C->self, 4.0s cast, range 8 circle
Bury3 = 36499, // 233C->self, 4.0s cast, range 25 width 6 rect
Bury4 = 36500, // 233C->self, 4.0s cast, range 35 width 10 rect
Bury5 = 36501, // 233C->self, 4.0s cast, range 4 circle
Bury6 = 36502, // 233C->self, 4.0s cast, range 6 circle
Bury7 = 36503, // 233C->self, 4.0s cast, range 25 width 6 rect
Bury8 = 36504, // 233C->self, 4.0s cast, range 35 width 10 rect
Decay = 36505, // 4194->self, 7.0s cast, range ?-40 donut
ShoreShaker = 36514, // Boss->self, 4.0+1.0s cast, single-target
ShoreShaker1 = 36515, // 233C->self, 5.0s cast, range 10 circle
ShoreShaker2 = 36516, // 233C->self, 7.0s cast, range ?-20 donut
ShoreShaker3 = 36517, // 233C->self, 9.0s cast, range ?-30 donut
}

class PunutiyFlop(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.PunutiyPress));
class Hydrowave(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Hydrowave), new AOEShapeCone(60, 15.Degrees()));

class Bury(BossModule module) : Components.GenericAOEs(module)
{
private readonly List<(Actor Caster, AOEInstance AOE)> _activeAOEs = [];

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => _activeAOEs.Select(x => x.AOE);

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
AOEShape? toAdd = (AID)spell.Action.ID switch
{
AID.Bury1 => new AOEShapeCircle(12),
AID.Bury2 => new AOEShapeCircle(8),
AID.Bury3 or AID.Bury7 => new AOEShapeRect(25, 3),
AID.Bury4 or AID.Bury8 => new AOEShapeRect(35, 5),
AID.Bury5 => new AOEShapeCircle(4),
AID.Bury6 => new AOEShapeCircle(6),
_ => null
};
if (toAdd != null)
_activeAOEs.Add((caster, new AOEInstance(toAdd, caster.Position, spell.Rotation, spell.NPCFinishAt)));
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
_activeAOEs.RemoveAll(x => x.Caster == caster);
}
}

class Resurface(BossModule module) : Components.GenericAOEs(module)
{
private AOEInstance? _aoe;

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

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if (spell.Action.ID == (uint)AID.Resurface)
_aoe = new AOEInstance(new AOEShapeCone(100, 32.Degrees()), caster.Position, spell.Rotation, spell.NPCFinishAt);
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (spell.Action.ID == (uint)AID.Resurface2)
_aoe = null;
}
}

class Decay(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Decay), new AOEShapeDonut(5, 40))
{
public override void DrawArenaForeground(int pcSlot, Actor pc)
=> Arena.Actors(Module.Enemies(OID.IhuykatumuFlytrap).Where(x => !x.IsDead), ArenaColor.Object, allowDeadAndUntargetable: true);
}

abstract class TetherBait(BossModule module, bool centerAtTarget = false) : Components.GenericBaitAway(module, default, true, centerAtTarget)
{
public override void DrawArenaForeground(int pcSlot, Actor pc)
{
base.DrawArenaForeground(pcSlot, pc);
foreach (var b in ActiveBaits)
{
if (Arena.Config.ShowOutlinesAndShadows)
Arena.AddLine(b.Source.Position, b.Target.Position, 0xFF000000, 2);
Arena.AddLine(b.Source.Position, b.Target.Position, ArenaColor.Danger);
}
}

public override void OnTethered(Actor source, ActorTetherInfo tether)
{
if (tether.ID != 17)
return;

var tar = WorldState.Actors.Find(tether.Target);
if (tar == null)
return;

OnTetherCreated(source, tar);
}

public override void OnUntethered(Actor source, ActorTetherInfo tether)
{
if (tether.ID == 17)
CurrentBaits.Clear();
}

protected abstract void OnTetherCreated(Actor source, Actor target);
}

class FlopBait(BossModule module) : TetherBait(module, true)
{
protected override void OnTetherCreated(Actor source, Actor target)
{
switch ((OID)source.OID)
{
case OID.ProdigiousPunutiy:
CurrentBaits.Add(new(source, target, new AOEShapeCircle(14)));
break;
case OID.PetitPunutiy:
CurrentBaits.Add(new(source, target, new AOEShapeCircle(6)));
break;
}
}
}

class HydroBait(BossModule module) : TetherBait(module, false)
{
protected override void OnTetherCreated(Actor source, Actor target)
{
if ((OID)source.OID == OID.Punutiy)
CurrentBaits.Add(new(source, target, new AOEShapeCone(60, 15.Degrees())));
}
}

class ShoreShaker(BossModule module) : Components.ConcentricAOEs(module, [new AOEShapeCircle(10), new AOEShapeDonut(10, 20), new AOEShapeDonut(20, 30)])
{
public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.ShoreShaker)
AddSequence(Module.Center, spell.NPCFinishAt);
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
var order = (AID)spell.Action.ID switch
{
AID.ShoreShaker1 => 0,
AID.ShoreShaker2 => 1,
AID.ShoreShaker3 => 2,
_ => -1
};
if (!AdvanceSequence(order, caster.Position, WorldState.FutureTime(2f)))
ReportError($"unexpected order {order}");
}
}

class D011PrimePunutiyStates : StateMachineBuilder
{
public D011PrimePunutiyStates(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<PunutiyFlop>()
.ActivateOnEnter<Hydrowave>()
.ActivateOnEnter<Resurface>()
.ActivateOnEnter<Bury>()
.ActivateOnEnter<Decay>()
.ActivateOnEnter<FlopBait>()
.ActivateOnEnter<HydroBait>()
.ActivateOnEnter<ShoreShaker>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.WIP, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 826, NameID = 12723)]
public class D011PrimePunutiy(WorldState ws, Actor primary) : BossModule(ws, primary, new(35, -95), new ArenaBoundsSquare(20));
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// there is nothing here. boss 2 can trivially be solved by AI
130 changes: 130 additions & 0 deletions BossMod/Modules/Dawntrail/Dungeon/D01Ihuykatumu/D013Apollyon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
namespace BossMod.Dawntrail.Dungeon.D01Ihuykatumu.D013Apollyon;

public enum OID : uint
{
Helper = 0x233C, // R0.500, x?, 523 type
Boss = 0x4165, // R7.000, x?
LightningAOE = 0x1EBA21, // R0.500, x?, EventObj type
Whirlwind = 0x416C, // R1.000, x?
}

public enum AID : uint
{
RazorZephyr = 36340, // 4165->self, 4.0s cast, range 50 width 12 rect
BladeST = 36347, // 4165->none, 4.5s cast, single-target
HighWind = 36341, // 4165->self, 5.0s cast, range 60 circle
BladesOfFamine = 36346, // 233C->self, 3.0s cast, range 50 width 12 rect
LevinsickleSpark = 36349, // 233C->location, 5.0s cast, range 4 circle
Levinsickle = 36350, // 233C->location, 5.0s cast, range 4 circle
WingOfLightning = 36351, // 233C->self, 8.0s cast, range 40 ?-degree cone
ThunderIII = 36353, // 233C->player, 5.0s cast, range 6 circle
BladeAOE = 36357, // 233C->none, 5.0s cast, range 6 circle
WindSickle = 36358, // 233C->self, 4.0s cast, range ?-60 donut
RazorStorm = 36355, // 4165->self, 5.0s cast, range 40 width 40 rect
Windwhistle = 36359, // 4165->self, 4.0s cast, single-target
Cuttingwind = 36360, // 233C->self, no cast, range 72 width 8 rect
}

class RazorZephyr(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.RazorZephyr), new AOEShapeRect(50, 6));
class Blade(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.BladeST));
class HighWind(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.HighWind));
class BladesOfFamine(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BladesOfFamine), new AOEShapeRect(50, 6));
class WingOfLightning(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.WingOfLightning), new AOEShapeCone(40, 22.5f.Degrees()), maxCasts: 8);
class LightningHelper(BossModule module) : Components.PersistentVoidzone(module, 4, m => m.Enemies(OID.LightningAOE));
class ThunderIII(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.ThunderIII), 6);
class BladeAOE(BossModule module) : Components.BaitAwayCast(module, ActionID.MakeSpell(AID.BladeAOE), new AOEShapeCircle(6), centerAtTarget: true);
class WindSickle(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.WindSickle), new AOEShapeDonut(6, 60));
class RazorStorm(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.RazorStorm), new AOEShapeRect(40, 20));
class Levinsickle(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Levinsickle), 4);
class LevinsickleSpark(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.LevinsickleSpark), 4);

// first aoe is 10 seconds after windwhistle
// rest are 8 seconds after previous
class Whirlwind(BossModule module) : Components.GenericAOEs(module)
{
private int _activations;
private DateTime _nextActivation;

private static readonly List<Angle> Rotations = [0.Degrees(), 45.Degrees(), 90.Degrees(), 135.Degrees()];

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.Windwhistle)
_nextActivation = WorldState.FutureTime(10);
}

public override void DrawArenaBackground(int pcSlot, Actor pc)
{
foreach (var c in ActiveAOEs(pcSlot, pc))
{
c.Shape.Draw(Arena, c.Origin, c.Rotation, c.Color);
c.Shape.Outline(Arena, c.Origin, c.Rotation, ArenaColor.AOE);
}
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID == AID.Cuttingwind)
{
_activations += 1;
_nextActivation = WorldState.FutureTime(8);
}
}

public override void OnActorDestroyed(Actor actor)
{
_activations = 0;
_nextActivation = default;
}

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
if (_activations >= 12)
yield break;

var whirlwind = Module.Enemies(OID.Whirlwind).FirstOrDefault();
if (whirlwind == null)
yield break;

var whirlyHelper = Module.Enemies(OID.Helper).FirstOrDefault(x => x.NameID == 12715);
if (whirlyHelper == null)
yield break;

foreach (var angle in Rotations)
{
yield return new AOEInstance(new AOEShapeRect(72, 4, 72), whirlwind.Position, angle, _nextActivation, Shade(_nextActivation), _nextActivation < WorldState.FutureTime(4));
}
}

private uint Shade(DateTime activation)
{
var clampedETA = Math.Clamp((activation - WorldState.CurrentTime).TotalSeconds, 0, 4);
var opacity = 1 - clampedETA / 4;
var alpha = (uint)(opacity * 96) + 32;
return 0x008080 + alpha * 0x1000000;
}
}

class D013ApollyonStates : StateMachineBuilder
{
public D013ApollyonStates(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<RazorZephyr>()
.ActivateOnEnter<Blade>()
.ActivateOnEnter<HighWind>()
.ActivateOnEnter<BladesOfFamine>()
.ActivateOnEnter<WingOfLightning>()
.ActivateOnEnter<LightningHelper>()
.ActivateOnEnter<ThunderIII>()
.ActivateOnEnter<BladeAOE>()
.ActivateOnEnter<WindSickle>()
.ActivateOnEnter<RazorStorm>()
.ActivateOnEnter<Levinsickle>()
.ActivateOnEnter<LevinsickleSpark>()
.ActivateOnEnter<Whirlwind>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.WIP, Contributors = "xan", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 826, NameID = 12711)]
public class D013Apollyon(WorldState ws, Actor primary) : BossModule(ws, primary, new(-107, 265), new ArenaBoundsCircle(20));
Loading
Loading