Skip to content

Commit

Permalink
Merge pull request #130 from FFXIV-CombatReborn/mergeWIP2
Browse files Browse the repository at this point in the history
Ktisis Hyperboreia
  • Loading branch information
CarnifexOptimus authored Jun 12, 2024
2 parents b3f9acb + 8c83156 commit 437e749
Show file tree
Hide file tree
Showing 12 changed files with 278 additions and 143 deletions.
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

0 comments on commit 437e749

Please sign in to comment.