Skip to content

Commit

Permalink
Merge pull request #59 from CarnifexOptimus/slave_lotsofchanges
Browse files Browse the repository at this point in the history
Masked Carnivale 31+32
  • Loading branch information
CarnifexOptimus authored Apr 26, 2024
2 parents c21b8ac + 4831e2c commit 590aa1e
Show file tree
Hide file tree
Showing 12 changed files with 584 additions and 5 deletions.
2 changes: 1 addition & 1 deletion BossMod/BossModule/AOEShapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public void SetEndPointFromCastLocation(Actor caster)

public override string ToString() => $"Cross: l={Length:f3}, w={HalfWidth * 2}, off={DirectionOffset}";
public override bool Check(WPos position, WPos origin, Angle rotation) => position.InRect(origin, rotation + DirectionOffset, Length, Length, HalfWidth) || position.InRect(origin, rotation + DirectionOffset, HalfWidth, HalfWidth, Length);
public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.Zone(arena.Bounds.ClipAndTriangulate(ContourPoints(origin, rotation)), color);
public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.ZoneCross(origin, Length, HalfWidth, rotation + DirectionOffset, color);

public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.Danger)
{
Expand Down
32 changes: 32 additions & 0 deletions BossMod/BossModule/ArenaBounds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,38 @@ public float ScreenHalfSize
cache[(start, end, halfWidth)] = result;
return result;
}

public List<(WPos, WPos, WPos)> ClipAndTriangulateCross(WPos origin, float length, float halfWidth, Angle rotation)
{
if (cache.TryGetValue((origin, length, halfWidth, rotation), out var cachedResult))
return cachedResult;
var dx = rotation.ToDirection();
var dy = dx.OrthoL();
var dx1 = dx * length;
var dx2 = dx * halfWidth;
var dy1 = dy * length;
var dy2 = dy * halfWidth;

var points = new List<WPos>
{
origin + dx1 - dy2,
origin + dx2 - dy2,
origin + dx2 - dy1,
origin - dx2 - dy1,
origin - dx2 - dy2,
origin - dx1 - dy2,
origin - dx1 + dy2,
origin - dx2 + dy2,
origin - dx2 + dy1,
origin + dx2 + dy1,
origin + dx2 + dy2,
origin + dx1 + dy2,
};

var result = ClipAndTriangulate(points);
cache[(origin, length, halfWidth, rotation)] = result;
return result;
}
}

public class ArenaBoundsCircle(WPos center, float radius) : ArenaBounds(center, radius)
Expand Down
1 change: 1 addition & 0 deletions BossMod/BossModule/MiniArena.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ public void Zone(List<(WPos, WPos, WPos)> triangulation, uint color)
public void ZoneRect(WPos origin, WDir direction, float lenFront, float lenBack, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(origin, direction, lenFront, lenBack, halfWidth), color);
public void ZoneRect(WPos origin, Angle direction, float lenFront, float lenBack, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(origin, direction, lenFront, lenBack, halfWidth), color);
public void ZoneRect(WPos start, WPos end, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(start, end, halfWidth), color);
public void ZoneCross(WPos origin, float length, float halfWidth, Angle rotation, uint color) => Zone(Bounds.ClipAndTriangulateCross(origin, length, halfWidth, rotation), color);

public void TextScreen(Vector2 center, string text, uint color, float fontSize = 17)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class FrigidStone2(BossModule module) : Components.LocationTargetedAOEs(module,

class OutInAOE(BossModule module) : Components.ConcentricAOEs(module, _shapes)
{
private static readonly AOEShape[] _shapes = { new AOEShapeCircle(10), new AOEShapeDonut(10, 20) };
private static readonly AOEShape[] _shapes = [new AOEShapeCircle(10), new AOEShapeDonut(10, 20)];

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Hints2(BossModule module) : BossComponent(module)
{
public override void AddGlobalHints(GlobalHints hints)
{
hints.Add("The imps are weak to fire spells and strong against ice.\nInterrupt Void Blizzard with Spitting Sardine.");
hints.Add("The imps are weak to fire spells and strong against ice.\nInterrupt Void Blizzard with Flying Sardine.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Hints2(BossModule module) : BossComponent(module)
public override void AddGlobalHints(GlobalHints hints)
{
if (!Module.Enemies(OID.ArenaImp).All(e => e.IsDead))
hints.Add("The imps are weak to fire spells and strong against ice.\nInterrupt Void Blizzard with Spitting Sardine.");
hints.Add("The imps are weak to fire spells and strong against ice.\nInterrupt Void Blizzard with Flying Sardine.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class FluidConvectionDynamic(BossModule module) : Components.GenericAOEs(module)
public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
if (shape != default)
yield return new(shape, Module.PrimaryActor.Position, Module.PrimaryActor.Rotation, _activation);
yield return new(shape, Module.PrimaryActor.Position, default, _activation);
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum OID : uint
FireVoidzone = 0x1E8D9B,
Helper = 0x233C,
}

public enum AID : uint
{
LawOfTheTorch = 18838, // Boss->self, 3,0s cast, range 34 20-degree cone
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
namespace BossMod.Global.MaskedCarnivale.Stage31.Act1;

public enum OID : uint
{
Boss = 0x30F5, //R=2.0
Imitation = 0x30F6, // R=2.0
Helper = 0x233C,
}

public enum AID : uint
{
AutoAttack = 6499, // 30F5->player, no cast, single-target
Mimic = 23097, // 30F5->self, 5,0s cast, single-target, stop everything that does dmg
MimickedFlameThrower = 23116, // 30F5->self, no cast, range 8 90-degree cone, unavoidable
MimickedSap0 = 23104, // 30F5->self, 3,5s cast, single-target
MimickedSap1 = 23101, // 233C->location, 4,0s cast, range 8 circle
MimickedSap2 = 23105, // 30F5->self, 1,5s cast, single-target
MimickedSap3 = 23106, // 233C->location, 2,0s cast, range 8 circle
MimickedDoomImpending = 23113, // 30F5->self, 8,0s cast, range 80 circle, applies doom
MimickedBunshin = 23107, // 30F5->self, 3,0s cast, single-target, summons Imitation
MimickedFireBlast = 23109, // 30F5->self, 2,0s cast, single-target
MimickedFireBlast2 = 23110, // 233C->self, 2,0s cast, range 70+R width 4 rect
MimickedProteanWave = 23111, // 30F6->self, 2,0s cast, single-target
MimickedProteanWave2 = 23112, // 233C->self, 2,0s cast, range 50 30-degree cone
MimickedRawInstinct = 23115, // 30F5->self, 3,0s cast, single-target, buffs self with critical strikes, can be removed
MimickedImpSong = 23114, // 30F5->self, 6,0s cast, range 40 circle, interruptible, turns player into imp
MimickedFlare = 23098, // Boss->player, 3,0s cast, range 80 circle, possible punishment for ignoring Mimic
MimickedHoly = 23100, // Boss->player, 3,0s cast, range 6 circle, possible punishment for ignoring Mimic
MimickedPowerfulHit = 23103, // Boss->player, 3,0s cast, single-target, possible punishment for ignoring Mimic
MimickedCriticalHit = 23102, // Boss->player, 3,0s cast, single-target, possible punishment for ignoring Mimic
}

public enum SID : uint
{
Mimicry = 2450, // none->Boss, extra=0x0
Doom = 1769, // Boss->player, extra=0x0
Incurable = 1488, // Boss->player, extra=0x0
CriticalStrikes = 1797, // Boss->Boss, extra=0x0
DamageUp = 443, // none->Boss, extra=0x1
Imp = 1103, // Boss->player, extra=0x2E
}

class Mimic(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.Mimic), "Stop attacking when cast ends");
class MimickedSap1(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.MimickedSap1), 8);
class MimickedSap2(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.MimickedSap3), 8);
class MimickedDoomImpending(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.MimickedDoomImpending), "Heal to full before cast ends!");
class MimickedProteanWave(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MimickedProteanWave2), new AOEShapeCone(50, 15.Degrees()));
class MimickedFireBlast(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MimickedFireBlast2), new AOEShapeRect(70.5f, 2));
class MimickedImpSong(BossModule module) : Components.CastInterruptHint(module, ActionID.MakeSpell(AID.MimickedImpSong));
class MimickedRawInstinct(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.MimickedRawInstinct), "Applies buff, dispel it");
class MimickedFlare(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.MimickedFlare), "Use Diamondback!");
class MimickedHoly(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.MimickedHoly), "Use Diamondback!");
class MimickedCriticalHit(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.MimickedCriticalHit), "Use Diamondback!");
class MimickedPowerfulHit(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.MimickedPowerfulHit), "Use Diamondback!");

class Hints2(BossModule module) : BossComponent(module)
{
public override void AddHints(int slot, Actor actor, TextHints hints)
{
var mimicry = Module.PrimaryActor.FindStatus(SID.Mimicry);
if (mimicry != null)
hints.Add($"Do no damage!");
var crit = Module.PrimaryActor.FindStatus(SID.CriticalStrikes);
if (crit != null)
hints.Add("Dispel buff!");
}
}

class Hints(BossModule module) : BossComponent(module)
{
public override void AddGlobalHints(GlobalHints hints)
{
hints.Add($"For this fight Diamondback, Exuviation, Flying Sardine and a healing\nability (preferably Pom Cure with healer mimicry) are mandatory.\nEerie Soundwave is also recommended.");
}

public override void AddHints(int slot, Actor actor, TextHints hints)
{
hints.Add("Requirements for achievement: Take no optional damage and finish faster\nthan ideal time.", false);
}
}

class Stage31Act1States : StateMachineBuilder
{
public Stage31Act1States(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<Mimic>()
.ActivateOnEnter<MimickedSap1>()
.ActivateOnEnter<MimickedSap2>()
.ActivateOnEnter<MimickedDoomImpending>()
.ActivateOnEnter<MimickedProteanWave>()
.ActivateOnEnter<MimickedFireBlast>()
.ActivateOnEnter<MimickedImpSong>()
.ActivateOnEnter<MimickedFireBlast>()
.ActivateOnEnter<MimickedRawInstinct>()
.ActivateOnEnter<MimickedCriticalHit>()
.ActivateOnEnter<MimickedPowerfulHit>()
.ActivateOnEnter<MimickedHoly>()
.ActivateOnEnter<MimickedFlare>()
.ActivateOnEnter<Hints2>()
.DeactivateOnEnter<Hints>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.MaskedCarnivale, GroupID = 754, NameID = 9908, SortOrder = 1)]
public class Stage31Act1 : BossModule
{
public Stage31Act1(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(100, 100), 16))
{
ActivateComponent<Hints>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
namespace BossMod.Global.MaskedCarnivale.Stage31.Act2;

public enum OID : uint
{
Boss = 0x30F7, //R=2.0
Maelstrom = 0x30F9, //R=1.0
Voidzone = 0x1E9684,
Helper = 0x233C,
}

public enum AID : uint
{
AutoAttack = 6499, // Boss->player, no cast, single-target
Teleport = 23108, // Boss->location, no cast, single-target
GogoFireIII = 23117, // Boss->self, 3,0s cast, range 60 circle, applies pyretic
GogoBlizzardIII = 23118, // Boss->self, 3,0s cast, range 6+R circle
GogoThunderIII = 23119, // Boss->location, 3,0s cast, range 6 circle
GogoFlare = 23128, // Boss->player, 6,0s cast, range 80 circle
GogoHoly = 23130, // Boss->player, 6,0s cast, range 6 circle, unavoidable, basically a raidwide
GogoMeteorVisual = 23120, // Boss->self, 3,0s cast, single-target
GogoMeteorVisual2 = 22023, // Boss->self, no cast, single-target
GogoMeteor1 = 23121, // Helper->location, 3,0s cast, range 5 circle
GogoMeteor2 = 23123, // Helper->location, 12,0s cast, range 100 circle, damage fall off AOE, ca. 16 distance seems fine
GogoMeteor3 = 23131, // Helper->location, 4,0s cast, range 8 circle
GogoMeteor4 = 23122, // Helper->location, 11,0s cast, range 8 circle
GogoMeteor5 = 23129, // Helper->location, 7,0s cast, range 100 circle, damage fall off AOE, ca. 16 distance seems fine
GogoMeteor6 = 23124, // Helper->location, 10,0s cast, range 100 circle, wipe without diamondback
Charybdis = 20055, // Boss->self, 3,0s cast, single-target
Charybdis2 = 20056, // Helper->self, 4,0s cast, range 8 circle
Icestorm = 23126, // Boss->self, 3,0s cast, single-target
Icestorm2 = 23127, // Helper->self, no cast, range 60 circle, applies frostbite + heavy, should be removed with exuviation
AetherialPull = 23125, // Maelstrom->self, 1,0s cast, range 8 circle, pull 40 between centers
}

public enum SID : uint
{
Pyretic = 960, // Boss->player, extra=0x0
Frostbite = 268, // Helper->player, extra=0x0
Heavy = 1107, // Helper->player, extra=0x50
}

class Charybdis(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Charybdis), 8);
class Maelstrom(BossModule module) : Components.PersistentVoidzone(module, 8, m => m.Enemies(OID.Maelstrom));
class GogoFlare(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.GogoFlare));
class GogoHoly(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.GogoHoly));
class GogoMeteor1(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.GogoMeteor1), 5);
class GogoMeteor2(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.GogoMeteor2), 16);
class GogoMeteor3(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.GogoMeteor3), 8);
class GogoMeteor4(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.GogoMeteor4), 8);
class GogoMeteor5(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.GogoMeteor5), 16);
class GogoMeteorBig(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.GogoMeteor6), "Use Diamondback!");
class Icestorm(BossModule module) : Components.RaidwideCastDelay(module, ActionID.MakeSpell(AID.Icestorm), ActionID.MakeSpell(AID.Icestorm2), 0.9f, "Raidwide + Frostbite + Heavy");
class ThunderIII(BossModule module) : Components.PersistentVoidzoneAtCastTarget(module, 6, ActionID.MakeSpell(AID.GogoThunderIII), m => m.Enemies(OID.Voidzone).Where(e => e.EventState != 7), 0.8f);

class GogoBlizzardIII(BossModule module) : Components.GenericAOEs(module)
{
private static readonly AOEShapeCircle circle = new(8);
private DateTime _activation;

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
if (_activation != default)
yield return new(circle, Module.PrimaryActor.Position, default, _activation);
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.GogoFireIII)
_activation = spell.NPCFinishAt.AddSeconds(5.1f);
}

public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID == AID.GogoBlizzardIII)
_activation = default;
}
}

class GogoFireIIIHint(BossModule module) : Components.CastHint(module, ActionID.MakeSpell(AID.GogoFireIII), "Pyretic, dodge AOE then stop everything!");

class Pyretic(BossModule module) : Components.StayMove(module)
{
public override void OnStatusGain(Actor actor, ActorStatus status)
{
if ((SID)status.ID is SID.Pyretic)
{
if (Raid.FindSlot(actor.InstanceID) is var slot && slot >= 0 && slot < Requirements.Length)
Requirements[slot] = Requirement.Stay;
}
}

public override void OnStatusLose(Actor actor, ActorStatus status)
{
if ((SID)status.ID is SID.Pyretic)
{
if (Raid.FindSlot(actor.InstanceID) is var slot && slot >= 0 && slot < Requirements.Length)
Requirements[slot] = Requirement.None;
}
}
}

class Hints(BossModule module) : BossComponent(module)
{
public override void AddHints(int slot, Actor actor, TextHints hints)
{
var debuff = actor.FindStatus(SID.Heavy) != null || actor.FindStatus(SID.Frostbite) != null;
if (debuff)
hints.Add($"Cleanse debuffs!");
}
}

class Stage31Act2States : StateMachineBuilder
{
public Stage31Act2States(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<Charybdis>()
.ActivateOnEnter<Maelstrom>()
.ActivateOnEnter<GogoFlare>()
.ActivateOnEnter<GogoHoly>()
.ActivateOnEnter<GogoMeteor1>()
.ActivateOnEnter<GogoMeteor2>()
.ActivateOnEnter<GogoMeteor3>()
.ActivateOnEnter<GogoMeteor4>()
.ActivateOnEnter<GogoMeteor5>()
.ActivateOnEnter<GogoMeteorBig>()
.ActivateOnEnter<ThunderIII>()
.ActivateOnEnter<Icestorm>()
.ActivateOnEnter<GogoBlizzardIII>()
.ActivateOnEnter<GogoFireIIIHint>()
.ActivateOnEnter<Pyretic>()
.ActivateOnEnter<Hints>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.Contributed, Contributors = "Malediktus", GroupType = BossModuleInfo.GroupType.MaskedCarnivale, GroupID = 754, NameID = 9908, SortOrder = 2)]
public class Stage31Act2(WorldState ws, Actor primary) : BossModule(ws, primary, new ArenaBoundsCircle(new(100, 100), 16));
Loading

0 comments on commit 590aa1e

Please sign in to comment.