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

BA Ozma module #621

Merged
merged 1 commit into from
Feb 22, 2025
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
64 changes: 53 additions & 11 deletions BossMod/Components/BaitAway.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,38 +75,80 @@ public List<Bait> ActiveBaitsNotOn(Actor target)

public WPos BaitOrigin(Bait bait) => (CenterAtTarget ? bait.Target : bait.Source).Position;
public bool IsClippedBy(Actor actor, Bait bait) => bait.Shape.Check(actor.Position, BaitOrigin(bait), bait.Rotation);
public IEnumerable<Actor> PlayersClippedBy(Bait bait) => Raid.WithoutSlot().Exclude(bait.Target).InShape(bait.Shape, BaitOrigin(bait), bait.Rotation);
public List<Actor> PlayersClippedBy(Bait bait)
{
var actors = Raid.WithoutSlot();
var len = actors.Length;
List<Actor> result = new(len);
for (var i = 0; i < len; ++i)
{
var actor = actors[i];
if (actor != bait.Target && bait.Shape.Check(actor.Position, BaitOrigin(bait), bait.Rotation))
result.Add(actor);
}

return result;
}

public override void AddHints(int slot, Actor actor, TextHints hints)
{
if (!EnableHints)
return;

var count = ActiveBaits.Count;
if (count == 0)
return;
if (ForbiddenPlayers[slot])
{
if (ActiveBaitsOn(actor).Count != 0)
var activeBaits = ActiveBaitsOn(actor);
if (activeBaits.Count != 0)
hints.Add("Avoid baiting!");
}
else
{
if (ActiveBaitsOn(actor).Any(b => PlayersClippedBy(b).Any()))
hints.Add(BaitAwayHint);
var activeBaits = ActiveBaitsOn(actor);
for (var i = 0; i < activeBaits.Count; ++i)
{
var clippedPlayers = PlayersClippedBy(activeBaits[i]);
if (clippedPlayers.Count != 0)
{
hints.Add(BaitAwayHint);
break;
}
}
}

if (!IgnoreOtherBaits && ActiveBaitsNotOn(actor).Any(b => IsClippedBy(actor, b)))
hints.Add("GTFO from baited aoe!");
if (!IgnoreOtherBaits)
{
var otherActiveBaits = ActiveBaitsNotOn(actor);
for (var i = 0; i < otherActiveBaits.Count; ++i)
{
if (IsClippedBy(actor, otherActiveBaits[i]))
{
hints.Add("GTFO from baited aoe!");
break;
}
}
}
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (ActiveBaits.Count == 0)
return;
var activeBaitsNotOnActor = ActiveBaitsNotOn(actor);
var activeBaitsOnActor = ActiveBaitsOn(actor);
var countActiveBaitsNotOnActor = activeBaitsNotOnActor.Count;
var countActiveBaitsOnActor = activeBaitsOnActor.Count;

foreach (var bait in ActiveBaitsNotOn(actor))
for (var i = 0; i < countActiveBaitsNotOnActor; ++i)
{
var bait = activeBaitsNotOnActor[i];
hints.AddForbiddenZone(bait.Shape, BaitOrigin(bait), bait.Rotation, bait.Activation);

foreach (var bait in ActiveBaitsOn(actor))
AddTargetSpecificHints(actor, bait, hints);
}
for (var i = 0; i < countActiveBaitsOnActor; ++i)
{
AddTargetSpecificHints(actor, activeBaitsOnActor[i], hints);
}
}

private void AddTargetSpecificHints(Actor actor, Bait bait, AIHints hints)
Expand Down
16 changes: 13 additions & 3 deletions BossMod/Components/Towers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell)
}

// for tower mechanics in open world since likely not everyone is in your party
public class GenericTowersOpenWorld(BossModule module, ActionID aid = default, bool prioritizeInsufficient = false) : CastCounter(module, aid)
public class GenericTowersOpenWorld(BossModule module, ActionID aid = default, bool prioritizeInsufficient = false, bool prioritizeEmpty = false) : CastCounter(module, aid)
{
public struct Tower(WPos position, float radius, int minSoakers = 1, int maxSoakers = 1, HashSet<Actor>? allowedSoakers = null, DateTime activation = default)
{
Expand Down Expand Up @@ -315,6 +315,7 @@ public int NumInside(BossModule module)

public readonly List<Tower> Towers = [];
public readonly bool PrioritizeInsufficient = prioritizeInsufficient; // give priority to towers with more than 0 but less than min soakers
public readonly bool PrioritizeEmpty = prioritizeEmpty; // give priority to towers with 0 soakers

// default tower styling
public static void DrawTower(MiniArena arena, WPos pos, float radius, bool safe)
Expand Down Expand Up @@ -420,7 +421,16 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
}
if (!hasForbiddenSoakers)
{
if (PrioritizeInsufficient)
if (PrioritizeEmpty)
{
for (var i = 0; i < count; ++i)
{
var t = Towers[i];
if (t.NumInside(Module) == 0)
forbiddenInverted.Add(ShapeDistance.InvertedCircle(t.Position, t.Radius));
}
}
else if (PrioritizeInsufficient) // less soakers than max
{
List<Tower> insufficientTowers = new(count);
for (var i = 0; i < count; ++i)
Expand Down Expand Up @@ -510,7 +520,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
}
}

public class CastTowersOpenWorld(BossModule module, ActionID aid, float radius, int minSoakers = 1, int maxSoakers = 1) : GenericTowersOpenWorld(module, aid)
public class CastTowersOpenWorld(BossModule module, ActionID aid, float radius, int minSoakers = 1, int maxSoakers = 1, bool prioritizeInsufficient = false, bool prioritizeEmpty = false) : GenericTowersOpenWorld(module, aid, prioritizeInsufficient, prioritizeEmpty)
{
public readonly float Radius = radius;
public readonly int MinSoakers = minSoakers;
Expand Down
56 changes: 48 additions & 8 deletions BossMod/Data/ActorEnumeration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,64 @@ public static IEnumerable<Actor> InRadiusExcluding(this IEnumerable<Actor> range
}

// select actors in specified shape
public static IEnumerable<Actor> InShape(this IEnumerable<Actor> range, AOEShape shape, Actor origin)
public static List<Actor> InShape(this IEnumerable<Actor> range, AOEShape shape, Actor origin)
{
return range.Where(actor => shape.Check(actor.Position, origin));
List<Actor> result = [];

foreach (var actor in range)
{
if (shape.Check(actor.Position, origin))
{
result.Add(actor);
}
}

return result;
}

public static IEnumerable<(int, Actor)> InShape(this IEnumerable<(int, Actor)> range, AOEShape shape, Actor origin)
public static List<(int, Actor)> InShape(this IEnumerable<(int, Actor)> range, AOEShape shape, Actor origin)
{
return range.WhereActor(actor => shape.Check(actor.Position, origin));
List<(int, Actor)> result = [];

foreach (var tuple in range)
{
if (shape.Check(tuple.Item2.Position, origin))
{
result.Add(tuple);
}
}

return result;
}

public static IEnumerable<Actor> InShape(this IEnumerable<Actor> range, AOEShape shape, WPos origin, Angle rotation)
public static List<Actor> InShape(this IEnumerable<Actor> range, AOEShape shape, WPos origin, Angle rotation)
{
return range.Where(actor => shape.Check(actor.Position, origin, rotation));
List<Actor> result = [];

foreach (var actor in range)
{
if (shape.Check(actor.Position, origin, rotation))
{
result.Add(actor);
}
}

return result;
}

public static IEnumerable<(int, Actor)> InShape(this IEnumerable<(int, Actor)> range, AOEShape shape, WPos origin, Angle rotation)
public static List<(int, Actor)> InShape(this IEnumerable<(int, Actor)> range, AOEShape shape, WPos origin, Angle rotation)
{
return range.WhereActor(actor => shape.Check(actor.Position, origin, rotation));
List<(int, Actor)> result = [];

foreach (var tuple in range)
{
if (shape.Check(tuple.Item2.Position, origin, rotation))
{
result.Add(tuple);
}
}

return result;
}

// select actors that have tether with specific ID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ public override void Update()
CurrentBaits.Clear();
var deadline = WorldState.FutureTime(7d);
foreach (var (i, p) in Raid.WithSlot(false, false, true))
if (_activation[i] != default && _activation[i] < deadline)
CurrentBaits.Add(new(p, p, _shape, _activation[i]));
{
ref var activation = ref _activation[i];
if (activation != default && activation < deadline)
CurrentBaits.Add(new(p, p, _shape, activation));
}
}

public override void OnStatusGain(Actor actor, ActorStatus status)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ public override void Update()

for (var i = 0; i < 4; ++i)
{
var aoe = aoeChecks[i];
if (ActiveAOEs(0, Raid.Player()!).Any(c => c.Shape == aoe.AOE && c.Activation <= WorldState.CurrentTime))
var aoeCheck = aoeChecks[i];
if (_aoe is AOEInstance aoe && aoe.Shape == aoeCheck.AOE && aoe.Activation <= WorldState.CurrentTime)
{
Arena.Bounds = aoe.Bounds;
Arena.Bounds = aoeCheck.Bounds;
_aoe = null;
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public enum IconID : uint
}

class CandescentRayLineStack(BossModule module) : Components.LineStack(module, null, ActionID.MakeSpell(AID.CandescentRayLineStack), minStackSize: 3, maxStackSize: 3);
class CandescentRayTB(BossModule module) : Components.CastSharedTankbuster(module, ActionID.MakeSpell(AID.CandescentRayTB), new AOEShapeRect(50, 4))
class CandescentRayTB(BossModule module) : Components.CastSharedTankbuster(module, ActionID.MakeSpell(AID.CandescentRayTB), new AOEShapeRect(50f, 4f))
{
public override void AddHints(int slot, Actor actor, TextHints hints)
{
Expand All @@ -70,19 +70,21 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
{
if (Target == null)
return;
var koana = Module.Enemies(OID.Koana).FirstOrDefault();
var wuk = Module.Enemies(OID.WukLamat).FirstOrDefault();
var koanas = Module.Enemies(OID.Koana);
var wuks = Module.Enemies(OID.WukLamat);
var koana = koanas.Count != 0 ? koanas[0] : null;
var wuk = wuks.Count != 0 ? wuks[0] : null;
var primary = Module.PrimaryActor;
if (koana != null)
hints.AddForbiddenZone(ShapeDistance.Cone(primary.Position, 100, primary.AngleTo(koana), Angle.Asin(4 / (koana.Position - primary.Position).Length())), Activation);
hints.AddForbiddenZone(ShapeDistance.Cone(primary.Position, 100f, primary.AngleTo(koana), Angle.Asin(4f / (koana.Position - primary.Position).Length())), Activation);
if (wuk != null)
hints.AddForbiddenZone(ShapeDistance.InvertedRect(primary.Position, primary.AngleTo(wuk), 50, 0, 4), Activation);
hints.AddForbiddenZone(ShapeDistance.InvertedRect(primary.Position, primary.AngleTo(wuk), 50f, default, 4f), Activation);
}
}

class SearingSwell(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.SearingSwell), new AOEShapeCone(40, 22.5f.Degrees()));
class Ensnare(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Ensnare), 6);
class TriceraSnare(BossModule module) : Components.SpreadFromIcon(module, (uint)IconID.Spreadmarker, ActionID.MakeSpell(AID.TriceraSnare), 6, 4.7f)
class SearingSwell(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.SearingSwell), new AOEShapeCone(40f, 22.5f.Degrees()));
class Ensnare(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Ensnare), 6f);
class TriceraSnare(BossModule module) : Components.SpreadFromIcon(module, (uint)IconID.Spreadmarker, ActionID.MakeSpell(AID.TriceraSnare), 6f, 4.7f)
{
public override void OnEventDirectorUpdate(uint updateID, uint param1, uint param2, uint param3, uint param4)
{
Expand All @@ -96,7 +98,7 @@ class PrimordialRoar2(BossModule module) : Components.RaidwideCast(module, Actio

class OrbCollecting(BossModule module) : BossComponent(module)
{
private readonly List<Actor> _orbs = module.Enemies(OID.Orbs);
private readonly List<Actor> _orbs = module.Enemies((uint)OID.Orbs);

private IEnumerable<Actor> ActiveOrbs => _orbs.Where(x => x.Tether.ID != 0);

Expand Down Expand Up @@ -132,9 +134,9 @@ public override void DrawArenaForeground(int pcSlot, Actor pc)

class FlameBlast(BossModule module) : Components.GenericAOEs(module)
{
private static readonly AOEShapeRect rect = new(20, 2, 20);
private static readonly AOEShapeRect rect = new(20f, 2f, 20f);
private readonly List<AOEInstance> _aoes = [];
private static readonly Angle a90 = 90.Degrees();
private static readonly Angle a90 = 90f.Degrees();

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
Expand All @@ -155,13 +157,13 @@ public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)

public override void OnActorCreated(Actor actor)
{
if ((OID)actor.OID == OID.BallOfFire)
_aoes.Add(new(rect, actor.Position, actor.Rotation + a90, WorldState.FutureTime(6.7f)));
if (actor.OID == (uint)OID.BallOfFire)
_aoes.Add(new(rect, WPos.ClampToGrid(actor.Position), actor.Rotation + a90, WorldState.FutureTime(6.7d)));
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (_aoes.Count != 0 && (AID)spell.Action.ID == AID.FlameBlast)
if (_aoes.Count != 0 && spell.Action.ID == (uint)AID.FlameBlast)
_aoes.RemoveAt(0);
}
}
Expand All @@ -185,13 +187,13 @@ public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.Firestorm)
if (spell.Action.ID == (uint)AID.Firestorm)
_aoes.Add(new(circle, caster.Position, spell.Rotation, Module.CastFinishAt(spell)));
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (_aoes.Count != 0 && (AID)spell.Action.ID == AID.Firestorm)
if (_aoes.Count != 0 && spell.Action.ID == (uint)AID.Firestorm)
_aoes.RemoveAt(0);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
if (count == 0)
return [];
var aoes = new AOEInstance[count];
for (var i = 0; i < _hearts.Count; ++i)
for (var i = 0; i < count; ++i)
{
var h = _hearts[i];
aoes[i] = new(capsule, h.Position, h.Rotation);
Expand Down
Loading