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

Ktisis Hyperboreia #130

Merged
merged 1 commit into from
Jun 12, 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
12 changes: 9 additions & 3 deletions BossMod/BossModule/AOEShapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public sealed record class AOEShapeTriCone(float SideLength, Angle HalfAngle, An
public override Func<WPos, float> Distance(WPos origin, Angle rotation) => ShapeDistance.Tri(origin, new(default, SideLength * (rotation + HalfAngle).ToDirection(), SideLength * (rotation - HalfAngle).ToDirection()));
}

public sealed record class AOEShapeCustom(IEnumerable<Shape> UnionShapes, IEnumerable<Shape>? DifferenceShapes = null) : AOEShape
public sealed record class AOEShapeCustom(IEnumerable<Shape> UnionShapes, IEnumerable<Shape>? DifferenceShapes = null, bool InvertForbiddenZone = false) : AOEShape
{
private static readonly Dictionary<object, object> _polygonCacheStatic = [];
private readonly Dictionary<object, object> _polygonCache = [];
Expand Down Expand Up @@ -208,8 +208,14 @@ float combinedDistanceFunc(WPos p)
var maxDifferenceDist = differenceDistanceFuncs.Count != 0 ? differenceDistanceFuncs.Max(func => -func(p)) : float.MinValue;
return Math.Max(minUnionDist, maxDifferenceDist);
}
_distanceFuncCache[cacheKey] = combinedDistanceFunc;

return combinedDistanceFunc;
float finalFunc(WPos p)
{
var result = combinedDistanceFunc(p);
return InvertForbiddenZone ? -result : result;
}

_distanceFuncCache[cacheKey] = finalFunc;
return finalFunc;
}
}
2 changes: 1 addition & 1 deletion BossMod/BossModule/Shapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public override RelSimplifiedComplexPolygon ToPolygon(WPos center)
=> GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))]));

public override Func<WPos, float> Distance()
=> ShapeDistance.Rect(Center, Rotation, HalfWidth, HalfWidth, HalfHeight);
=> ShapeDistance.Rect(Center, Rotation, HalfHeight, HalfHeight, HalfWidth);

public override string ComputeHash() => ComputeSHA512($"{nameof(Rectangle)}:{Center.X},{Center.Z},{HalfWidth},{HalfHeight},{Rotation.Rad}");
}
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Components/BaitAway.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether)
}

// component for mechanics requiring icon targets to bait their aoe away from raid
public class BaitAwayIcon(BossModule module, AOEShape shape, uint iconID, ActionID aid = default, float activationDelay = 5.1f) : GenericBaitAway(module, aid)
public class BaitAwayIcon(BossModule module, AOEShape shape, uint iconID, ActionID aid = default, float activationDelay = 5.1f, bool centerAtTarget = false) : GenericBaitAway(module, aid, centerAtTarget: centerAtTarget)
{
public AOEShape Shape = shape;
public uint IID = iconID;
Expand Down
50 changes: 50 additions & 0 deletions BossMod/Components/LineOfSightAOE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,53 @@ private void Refresh()
Modify(position, BlockerActors().Select(b => (b.Position, b.HitboxRadius)), caster?.CastInfo?.NPCFinishAt ?? default);
}
}

// generic component that shows line-of-sight safe spots for rect AOES, probably a bad solution but needed for Hermes in Ktis Hyperboreia
// TODO: rework to add support for arbitrary AOE shapes and blocker shapes (eg rectangles in shadowbringer alliance raid),
// add support for multiple AOE sources at the same time (I simplified Hermes from 4 AOEs into one)
// add support for blockers that spawn or get destroyed after cast already started (Hermes: again a cheat here by only using that meteor that exists for the whole mechanic)
public abstract class GenericLineOfSightRectAOE(BossModule module, ActionID aid) : GenericAOEs(module, aid, "Hide behind obstacle!")
{
public List<AOEInstance> InvertedAOE = [];
public List<Shape> UnionShapes = [];
public List<Shape> DifferenceShapes = [];

public abstract IEnumerable<Actor> BlockerActors();
public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => InvertedAOE.Take(1);

public override void AddHints(int slot, Actor actor, TextHints hints)
{
if (ActiveAOEs(slot, actor).Any(c => c.Risky && !c.Check(actor.Position)))
hints.Add(WarningText);
}

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if (spell.Action == WatchedAction)
{
foreach (var b in BlockerActors())
{
UnionShapes.Add(new RectangleSE(b.Position, b.Position + 1000 * caster.Rotation.ToDirection(), b.HitboxRadius));
DifferenceShapes.Add(new Circle(b.Position, b.HitboxRadius));
}
InvertedAOE.Add(new(new AOEShapeCustom(CopyShapes(UnionShapes), CopyShapes(DifferenceShapes), true), Module.Arena.Center, default, spell.NPCFinishAt, ArenaColor.SafeFromAOE));
UnionShapes.Clear();
DifferenceShapes.Clear();
}
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (spell.Action == WatchedAction)
{
InvertedAOE.RemoveAt(0);
}
}

private List<Shape> CopyShapes(List<Shape> shapes)
{
var copy = new List<Shape>();
copy.AddRange(shapes);
return copy;
}
}
5 changes: 0 additions & 5 deletions BossMod/Modules/Endwalker/Alliance/A23Halone/Octagons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,4 @@ private void RemoveOctagons(byte index)
break;
}
}

// public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
// {
// //TODO fix AI map creation
// }
}
11 changes: 6 additions & 5 deletions BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ private void AddAOEs((int, int) indices, DateTime activation)

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (Module.FindComponent<CosmicKissKnockback>()!.Sources(slot, actor).Any())
var component = Module.FindComponent<CosmicKissKnockback>()!;
if (component.Sources(slot, actor).Any() || component.Activation > Module.WorldState.CurrentTime) // 0.8s delay to wait for action effect
{ } // remove forbidden zones while knockback is active to not confuse the AI
else
base.AddAIHints(slot, actor, assignment, hints);
Expand Down Expand Up @@ -162,7 +163,7 @@ class CosmicKissRaidwide(BossModule module) : Components.RaidwideCast(module, Ac

class CosmicKissKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.CosmicKiss), 13)
{
private DateTime activation;
public DateTime Activation;
private static readonly Angle Degrees90 = 90.Degrees();
private static readonly Angle Degrees45 = 45.Degrees();
private static readonly Angle Degrees0 = 0.Degrees();
Expand All @@ -173,14 +174,14 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
base.OnCastStarted(caster, spell);
if (spell.Action == WatchedAction)
activation = spell.NPCFinishAt.AddSeconds(0.8f);
Activation = spell.NPCFinishAt.AddSeconds(0.8f);
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
var forbidden = new List<Func<WPos, float>>();
var component = Module.FindComponent<ChaoticUndercurrent>()?.ActiveAOEs(slot, actor)?.ToList();
if (component != null && component.Count != 0 && Sources(slot, actor).Any() || activation > Module.WorldState.CurrentTime) // 0.8s delay to wait for action effect
if (component != null && component.Count != 0 && Sources(slot, actor).Any() || Activation > Module.WorldState.CurrentTime) // 0.8s delay to wait for action effect
{
if (component!.Any(x => x.Origin.Z == -152) && component!.Any(x => x.Origin.Z == -162))
{
Expand All @@ -197,7 +198,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
else if (component!.Any(x => x.Origin.Z == -162) && component!.Any(x => x.Origin.Z == -172))
forbidden.Add(ShapeDistance.InvertedCone(Module.Center, 7, Degrees0, Degrees90));
if (forbidden.Count > 0)
hints.AddForbiddenZone(p => forbidden.Select(f => f(p)).Max(), activation);
hints.AddForbiddenZone(p => forbidden.Select(f => f(p)).Max(), Activation);
}
}
}
Expand Down
54 changes: 0 additions & 54 deletions BossMod/Modules/Endwalker/Dungeon/D04Ktisis/D041Lyssa.cs

This file was deleted.

69 changes: 0 additions & 69 deletions BossMod/Modules/Endwalker/Dungeon/D04Ktisis/D042Ladon.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
namespace BossMod.Endwalker.Dungeon.D04KtisisHyperboreia.D041Lyssa;

public enum OID : uint
{
Boss = 0x3323, // R=4.0
Helper = 0x233C,
IcePillar = 0x3324, // R2.000, x0 (spawn during fight)
}

public enum AID : uint
{
AutoAttack = 6497, // Boss->player, no cast, single-target
FrigidStomp = 25181, // Boss->self, 5.0s cast, range 50 circle, raidwide
FrostbiteAndSeek1 = 28304, // Helper->self, no cast, single-target
FrostbiteAndSeek2 = 25175, // Boss->self, 3.0s cast, single-target
HeavySmash = 25180, // Boss->players, 5.0s cast, range 6 circle, stack
IcePillar = 25179, // IcePillar->self, 3.0s cast, range 4 circle
Icicall = 25178, // Boss->self, 3.0s cast, single-target
PillarPierceAOE = 25375, // IcePillar->self, 5.0s cast, range 80 width 4 rect
PunishingSliceVisual = 25176, // Boss->self, no cast, single-target
PunishingSliceAOE = 25177, // Helper->self, 2.0s cast, range 50 width 50 rect
SkullDasher = 25182, // Boss->player, 5.0s cast, single-target, tankbuster

}

class PillarPierceAOE(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.PillarPierceAOE), new AOEShapeRect(40, 2));

class PunishingSlice(BossModule module) : Components.GenericAOEs(module)
{
private AOEInstance? _aoe;
private static readonly AOEShapeRect rect = new(50, 25);

private static readonly Dictionary<(byte index, uint state), (WPos origin, Angle rotation)> aoeSources = new()
{
{(0x00, 0x00200010), (new WPos(-154.825f, 42.75f), 60.Degrees())},
{(0x00, 0x01000080), (new WPos(-154.825f, 55.25f), 119.997f.Degrees())},
{(0x00, 0x00020001), (new WPos(-144, 36.5f), -0.003f.Degrees())},
{(0x01, 0x00200010), (new WPos(-144, 61.5f), -180.Degrees())},
{(0x01, 0x01000080), (new WPos(-133.175f, 55.25f), -120.003f.Degrees())},
{(0x01, 0x00020001), (new WPos(-133.175f, 42.75f), -60.005f.Degrees())}
};

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

public override void OnEventEnvControl(byte index, uint state)
{
if (aoeSources.TryGetValue((index, state), out var source))
{
var activation = NumCasts == 0 ? Module.WorldState.FutureTime(13) : Module.WorldState.FutureTime(16);
_aoe = new AOEInstance(rect, source.origin, source.rotation, activation);
}
}

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

class IcePillar(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.IcePillar), new AOEShapeCircle(4));
class HeavySmash(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.HeavySmash), 6, 4);
class SkullDasher(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.SkullDasher));
class FrigidStomp(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.FrigidStomp));

class D041LyssaStates : StateMachineBuilder
{
public D041LyssaStates(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter<PillarPierceAOE>()
.ActivateOnEnter<PunishingSlice>()
.ActivateOnEnter<IcePillar>()
.ActivateOnEnter<HeavySmash>()
.ActivateOnEnter<SkullDasher>()
.ActivateOnEnter<FrigidStomp>();
}
}

[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 787, NameID = 10396)]
public class D041Lyssa(WorldState ws, Actor primary) : BossModule(ws, primary, DefaultBounds.Center, DefaultBounds)
{
private static readonly List<Shape> union = [new Circle(new(-144, 49), 19.5f)];
private static readonly List<Shape> difference = [new Rectangle(new(-144, 28), 20, 2), new Rectangle(new(-144, 70), 20, 2)];
public static readonly ArenaBoundsComplex DefaultBounds = new(union, difference);
}
Loading