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

some fixes #599

Merged
merged 1 commit into from
Feb 10, 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
9 changes: 5 additions & 4 deletions BossMod/Components/ChasingAOEs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public WPos PredictedPosition()
{
var offset = Target.Position - PrevPos;
var distance = offset.Length();
return distance > MoveDist ? PrevPos + MoveDist * offset / distance : Target.Position;
var pos = distance > MoveDist ? PrevPos + MoveDist * offset / distance : Target.Position;
return WPos.ClampToGrid(pos);
}
}

Expand Down Expand Up @@ -111,7 +112,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if (spell.Action == ActionFirst)
{
var pos = spell.TargetID == caster.InstanceID ? caster.Position : WorldState.Actors.Find(spell.TargetID)?.Position ?? spell.LocXZ;
var pos = spell.LocXZ;
var (slot, target) = Raid.WithSlot().ExcludedFromMask(ExcludedTargets).MinBy(ip => (ip.Item2.Position - pos).LengthSq());
if (target != null)
{
Expand All @@ -127,7 +128,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell)
if (spell.Action == ActionFirst || spell.Action == ActionRest)
{
var pos = spell.MainTargetID == caster.InstanceID ? caster.Position : WorldState.Actors.Find(spell.MainTargetID)?.Position ?? spell.TargetXZ;
Advance(pos, MoveDistance, WorldState.CurrentTime);
Advance(WPos.ClampToGrid(pos), MoveDistance, WorldState.CurrentTime);
if (Chasers.Count == 0 && ResetExcludedTargets)
{
ExcludedTargets.Reset();
Expand Down Expand Up @@ -185,7 +186,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell)
if (spell.Action == ActionFirst || spell.Action == ActionRest)
{
var pos = spell.MainTargetID == caster.InstanceID ? caster.Position : WorldState.Actors.Find(spell.MainTargetID)?.Position ?? spell.TargetXZ;
Advance(pos, MoveDistance, WorldState.CurrentTime);
Advance(WPos.ClampToGrid(pos), MoveDistance, WorldState.CurrentTime);
if (Chasers.Count == 0 && ResetExcludedTargets)
{
ExcludedTargets.Clear();
Expand Down
4 changes: 2 additions & 2 deletions BossMod/Components/Exaflare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
var l = Lines[i];
if (l.ExplosionsLeft != 0)
exas[i] = (l.Next, l.NextExplosion, l.Rotation);
exas[i] = (WPos.ClampToGrid(l.Next), l.NextExplosion, l.Rotation);
}
return exas;
}
Expand All @@ -73,7 +73,7 @@ public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
pos += l.Advance;
time = time.AddSeconds(l.TimeToMove);
exas.Add((pos, time, l.Rotation));
exas.Add((WPos.ClampToGrid(pos), time, l.Rotation));
}
}
return exas;
Expand Down
30 changes: 20 additions & 10 deletions BossMod/Components/Gaze.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public record struct Eye(
WPos Position,
DateTime Activation = new(),
Angle Forward = new(), // if non-zero, treat specified side as 'forward' for hit calculations
float Range = 10000);
float Range = 10000,
ulong? ActorID = null);

public bool Inverted = inverted; // if inverted, player should face eyes instead of averting

Expand All @@ -35,12 +36,12 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
if (Inverted)
{
foreach (var eye in ActiveEyes(slot, actor).Where(eye => actor.Position.InCircle(eye.Position, eye.Range)))
hints.ForbiddenDirections.Add((Angle.FromDirection(actor.Position - eye.Position) - eye.Forward, 135.Degrees(), eye.Activation));
hints.ForbiddenDirections.Add((Angle.FromDirection(actor.Position - eye.Position) - eye.Forward, 135f.Degrees(), eye.Activation));
}
else
{
foreach (var eye in ActiveEyes(slot, actor).Where(eye => actor.Position.InCircle(eye.Position, eye.Range)))
hints.ForbiddenDirections.Add((Angle.FromDirection(eye.Position - actor.Position) - eye.Forward, 45.Degrees(), eye.Activation));
hints.ForbiddenDirections.Add((Angle.FromDirection(eye.Position - actor.Position) - eye.Forward, 45f.Degrees(), eye.Activation));
}
}

Expand All @@ -54,7 +55,7 @@ public override void DrawArenaForeground(int pcSlot, Actor pc)

if (pc.Position.InCircle(eye.Position, eye.Range))
{
var (min, max) = Inverted ? (45, 315) : (-45, 45);
var (min, max) = Inverted ? (45f, 315f) : (-45f, 45f);
Arena.PathArcTo(pc.Position, 1, (pc.Rotation + eye.Forward + min.Degrees()).Rad, (pc.Rotation + eye.Forward + max.Degrees()).Rad);
MiniArena.PathStroke(false, Colors.Enemy);
}
Expand Down Expand Up @@ -89,23 +90,32 @@ private Vector2 IndicatorScreenPos(WPos eye)
// gaze that happens on cast end
public class CastGaze(BossModule module, ActionID aid, bool inverted = false, float range = 10000) : GenericGaze(module, aid, inverted)
{
public readonly List<Actor> _casters = [];
public readonly List<Eye> Eyes = [];

public override IEnumerable<Eye> ActiveEyes(int slot, Actor actor) => _casters.Where(c => c.CastInfo?.TargetID != actor.InstanceID).Select(c => new Eye(EyePosition(c), Module.CastFinishAt(c.CastInfo), Range: range));
public override IEnumerable<Eye> ActiveEyes(int slot, Actor actor) => Eyes;

public override void OnCastStarted(Actor caster, ActorCastInfo spell)
{
if (spell.Action == WatchedAction)
_casters.Add(caster);
Eyes.Add(new(spell.LocXZ, Module.CastFinishAt(spell), default, range, caster.InstanceID));
}

public override void OnCastFinished(Actor caster, ActorCastInfo spell)
{
if (spell.Action == WatchedAction)
_casters.Remove(caster);
{
var count = Eyes.Count;
for (var i = 0; i < count; ++i)
{
var eye = Eyes[i];
if (eye.ActorID == caster.InstanceID)
{
Eyes.Remove(eye);
break;
}
}
}
}

protected WPos EyePosition(Actor caster) => caster.CastInfo == null || caster.CastInfo.TargetID == caster.InstanceID ? caster.Position : WorldState.Actors.Find(caster.CastInfo.TargetID)?.Position ?? caster.CastInfo.LocXZ;
}

// cast weakpoint component: a number of casts (with supposedly non-intersecting shapes), player should face specific side determined by active status to the caster for aoe he's in
Expand Down
4 changes: 2 additions & 2 deletions BossMod/Components/Knockback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,11 @@ public override IEnumerable<Source> Sources(int slot, Actor actor)
var minDist = MinDistance + (MinDistanceBetweenHitboxes ? actor.HitboxRadius + c.HitboxRadius : 0);
if (c.CastInfo!.TargetID == c.InstanceID)
{
yield return new(c.Position, Distance, Module.CastFinishAt(c.CastInfo), Shape, c.CastInfo.Rotation, KnockbackKind, minDist);
yield return new(c.CastInfo.LocXZ, Distance, Module.CastFinishAt(c.CastInfo), Shape, c.CastInfo.Rotation, KnockbackKind, minDist);
}
else
{
var origin = WorldState.Actors.Find(c.CastInfo.TargetID)?.Position ?? c.CastInfo.LocXZ;
var origin = c.CastInfo.LocXZ;
yield return new(origin, Distance, Module.CastFinishAt(c.CastInfo), Shape, Angle.FromDirection(origin - c.Position), KnockbackKind, minDist);
}
}
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Components/LineOfSightAOE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell)
public void Refresh()
{
var caster = ActiveCaster;
WPos? position = caster != null ? (WorldState.Actors.Find(caster.CastInfo!.TargetID)?.Position ?? caster.CastInfo!.LocXZ) : null;
WPos? position = caster != null ? caster.CastInfo!.LocXZ : null;
Modify(position, BlockerActors().Select(b => (b.Position, b.HitboxRadius)), Module.CastFinishAt(caster?.CastInfo));
}
}
14 changes: 7 additions & 7 deletions BossMod/Components/PersistentVoidzone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
var aoes = new List<AOEInstance>();
foreach (var source in Sources(Module))
{
aoes.Add(new(Shape, source.Position, source.Rotation));
aoes.Add(new(Shape, WPos.ClampToGrid(source.Position), source.Rotation));
}
return aoes;
}
Expand All @@ -25,7 +25,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
return;
var forbidden = new List<Func<WPos, float>>();
foreach (var s in Sources(Module))
forbidden.Add(Shape.Distance(s.Position, s.Rotation));
forbidden.Add(Shape.Distance(WPos.ClampToGrid(s.Position), s.Rotation));
hints.AddForbiddenZone(ShapeDistance.Union(forbidden));
}
}
Expand All @@ -47,11 +47,11 @@ public class PersistentVoidzoneAtCastTarget(BossModule module, float radius, Act
public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor)
{
foreach (var p in _predictedByEvent)
yield return new(Shape, p.pos, default, p.time);
yield return new(Shape, WPos.ClampToGrid(p.pos), default, p.time);
foreach (var p in _predictedByCast)
yield return new(Shape, WorldState.Actors.Find(p.caster.CastInfo!.TargetID)?.Position ?? p.caster.CastInfo.LocXZ, default, p.time);
yield return new(Shape, p.caster.CastInfo!.LocXZ, default, p.time);
foreach (var z in Sources(Module))
yield return new(Shape, z.Position);
yield return new(Shape, WPos.ClampToGrid(z.Position));
}

public override void Update()
Expand All @@ -77,7 +77,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell)
{
base.OnEventCast(caster, spell);
if (spell.Action == WatchedAction)
_predictedByEvent.Add((WorldState.Actors.Find(spell.MainTargetID)?.Position ?? spell.TargetXZ, WorldState.FutureTime(CastEventToSpawn)));
_predictedByEvent.Add((spell.TargetXZ, WorldState.FutureTime(CastEventToSpawn)));
}
}

Expand Down Expand Up @@ -107,7 +107,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme

foreach (var source in Sources(Module))
{
var shape = Shape.Distance(source.Position, source.Rotation);
var shape = Shape.Distance(WPos.ClampToGrid(source.Position), source.Rotation);
shapes.Add(shape);
}
if (shapes.Count == 0)
Expand Down
30 changes: 15 additions & 15 deletions BossMod/Components/StackSpread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ public readonly int NumInside(BossModule module)
for (var i = 0; i < party.Length; ++i)
{
var indexActor = party[i];
if (!ForbiddenPlayers[indexActor.Item1] && indexActor.Item2.Position.InCircle(Target.Position, Radius))
if (!ForbiddenPlayers[indexActor.Item1] && indexActor.Item2.Position.InCircle(WPos.ClampToGrid(Target.Position), Radius))
++count;
}
return count;
}
public readonly bool CorrectAmountInside(BossModule module) => NumInside(module) is var count && count >= MinSize && count <= MaxSize;
public readonly bool InsufficientAmountInside(BossModule module) => NumInside(module) is var count && count < MaxSize;
public readonly bool TooManyInside(BossModule module) => NumInside(module) is var count && count > MaxSize;
public readonly bool IsInside(WPos pos) => pos.InCircle(Target.Position, Radius);
public readonly bool IsInside(WPos pos) => pos.InCircle(WPos.ClampToGrid(Target.Position), Radius);
public readonly bool IsInside(Actor actor) => IsInside(actor.Position);
}

Expand Down Expand Up @@ -133,7 +133,7 @@ public override void AddHints(int slot, Actor actor, TextHints hints)
var numUnsatisfiedStacks = 0;
foreach (var s in ActiveStacks.Where(s => !s.ForbiddenPlayers[slot]))
{
if (actor.Position.InCircle(s.Target.Position, s.Radius))
if (actor.Position.InCircle(WPos.ClampToGrid(s.Target.Position), s.Radius))
++numParticipatingStacks;
else if (Raid.WithoutSlot().InRadiusExcluding(s.Target, s.Radius).Count() + 1 < s.MinSize)
++numUnsatisfiedStacks;
Expand All @@ -149,11 +149,11 @@ public override void AddHints(int slot, Actor actor, TextHints hints)
//hints.Add("Stack!", ActiveStacks.Count(s => !s.ForbiddenPlayers[slot] && actor.Position.InCircle(s.Target.Position, s.Radius)) != 1);
}

if (ActiveSpreads.Any(s => s.Target != actor && actor.Position.InCircle(s.Target.Position, s.Radius)))
if (ActiveSpreads.Any(s => s.Target != actor && actor.Position.InCircle(WPos.ClampToGrid(s.Target.Position), s.Radius)))
{
hints.Add("GTFO from spreads!");
}
else if (ActiveStacks.Any(s => s.Target != actor && s.ForbiddenPlayers[slot] && actor.Position.InCircle(s.Target.Position, s.Radius)))
else if (ActiveStacks.Any(s => s.Target != actor && s.ForbiddenPlayers[slot] && actor.Position.InCircle(WPos.ClampToGrid(s.Target.Position), s.Radius)))
{
hints.Add("GTFO from forbidden stacks!");
}
Expand All @@ -165,25 +165,25 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
// TODO: think how to improve this, current implementation works, but isn't particularly good - e.g. nearby players tend to move to same spot, turn around, etc.
// ideally we should provide per-mechanic spread spots, but for simple cases we should try to let melee spread close and healers/rdd spread far from main target...
foreach (var spreadFrom in ActiveSpreads.Where(s => s.Target != actor))
hints.AddForbiddenZone(ShapeDistance.Circle(spreadFrom.Target.Position, spreadFrom.Radius + ExtraAISpreadThreshold), spreadFrom.Activation);
hints.AddForbiddenZone(ShapeDistance.Circle(WPos.ClampToGrid(spreadFrom.Target.Position), spreadFrom.Radius + ExtraAISpreadThreshold), spreadFrom.Activation);
foreach (var spreadFrom in ActiveSpreads.Where(s => s.Target == actor))
foreach (var x in Raid.WithoutSlot())
if (!ActiveSpreads.Any(s => s.Target == x))
hints.AddForbiddenZone(ShapeDistance.Circle(x.Position, spreadFrom.Radius + ExtraAISpreadThreshold), spreadFrom.Activation);
hints.AddForbiddenZone(ShapeDistance.Circle(WPos.ClampToGrid(x.Position), spreadFrom.Radius + ExtraAISpreadThreshold), spreadFrom.Activation);
foreach (var avoid in ActiveStacks.Where(s => s.Target != actor && (s.ForbiddenPlayers[slot] || !s.IsInside(actor) && (s.CorrectAmountInside(Module) || s.TooManyInside(Module)) || s.IsInside(actor) && s.TooManyInside(Module))))
hints.AddForbiddenZone(ShapeDistance.Circle(avoid.Target.Position, avoid.Radius), avoid.Activation);
hints.AddForbiddenZone(ShapeDistance.Circle(WPos.ClampToGrid(avoid.Target.Position), avoid.Radius), avoid.Activation);

if (Stacks.FirstOrDefault(s => s.Target == actor) is var actorStack && actorStack.Target != null)
{
// forbid standing next to other stack markers or overlapping them
foreach (var stackWith in ActiveStacks.Where(s => s.Target != actor))
hints.AddForbiddenZone(ShapeDistance.Circle(stackWith.Target.Position, stackWith.Radius * 2), stackWith.Activation);
hints.AddForbiddenZone(ShapeDistance.Circle(WPos.ClampToGrid(stackWith.Target.Position), stackWith.Radius * 2), stackWith.Activation);
// if player got stackmarker and is playing with NPCs, go to a NPC to stack with them since they will likely not come to you
if (Raid.WithoutSlot().Any(x => x.Type == ActorType.Buddy))
{
var forbidden = new List<Func<WPos, float>>();
foreach (var stackWith in ActiveStacks.Where(s => s.Target == actor))
forbidden.Add(ShapeDistance.InvertedCircle(Raid.WithoutSlot().FirstOrDefault(x => !x.IsDead && !IsSpreadTarget(x) && !IsStackTarget(x))!.Position, actorStack.Radius / 3));
forbidden.Add(ShapeDistance.InvertedCircle(WPos.ClampToGrid(Raid.WithoutSlot().FirstOrDefault(x => !x.IsDead && !IsSpreadTarget(x) && !IsStackTarget(x))!.Position), actorStack.Radius * 0.33f));
if (forbidden.Count > 0)
hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), actorStack.Activation);
}
Expand All @@ -193,7 +193,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
var forbidden = new List<Func<WPos, float>>();
foreach (var s in ActiveStacks.Where(x => !x.ForbiddenPlayers[slot] && (x.IsInside(actor) && !x.TooManyInside(Module)
|| !x.IsInside(actor) && x.InsufficientAmountInside(Module))))
forbidden.Add(ShapeDistance.InvertedCircle(s.Target.Position, s.Radius - 0.25f));
forbidden.Add(ShapeDistance.InvertedCircle(WPos.ClampToGrid(s.Target.Position), s.Radius - 0.25f));
if (forbidden.Count > 0)
hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), ActiveStacks.FirstOrDefault().Activation);
}
Expand Down Expand Up @@ -236,25 +236,25 @@ public override void DrawArenaForeground(int pcSlot, Actor pc)
if (!AlwaysShowSpreads && Spreads.FindIndex(s => s.Target == pc) is var iSpread && iSpread >= 0)
{
// Draw only own circle if spreading; no one should be inside.
DrawCircle(pc.Position, Spreads[iSpread].Radius);
DrawCircle(WPos.ClampToGrid(pc.Position), Spreads[iSpread].Radius);
}
else
{
// Handle safe stack circles
foreach (var s in ActiveStacks.Where(x => x.Target == pc || !x.ForbiddenPlayers[pcSlot]
&& !IsSpreadTarget(pc) && !IsStackTarget(pc) && (x.IsInside(pc)
&& !x.TooManyInside(Module) || !x.IsInside(pc) && x.InsufficientAmountInside(Module))))
DrawCircle(s.Target.Position, s.Radius, Colors.Safe);
DrawCircle(WPos.ClampToGrid(s.Target.Position), s.Radius, Colors.Safe);

// Handle dangerous stack circles
foreach (var s in ActiveStacks.Where(x => x.Target != pc && (IsStackTarget(pc) || x.ForbiddenPlayers[pcSlot] || IsSpreadTarget(pc) ||
!x.IsInside(pc) && (x.CorrectAmountInside(Module) || x.TooManyInside(Module)) ||
x.IsInside(pc) && x.TooManyInside(Module))))
DrawCircle(s.Target.Position, s.Radius);
DrawCircle(WPos.ClampToGrid(s.Target.Position), s.Radius);

// Handle spread circles
foreach (var s in ActiveSpreads)
DrawCircle(s.Target.Position, s.Radius);
DrawCircle(WPos.ClampToGrid(s.Target.Position), s.Radius);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,13 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell)
}
void AddAOEs(AOEShape shape2, Angle initialAngle)
{
var pos = WPos.ClampToGrid(Arena.Center);
for (var i = 0; i < 3; ++i)
{
var activation = Module.CastFinishAt(spell, 1.8f + i * 0.3f);
var angle = initialAngle - i * a120;
_aoes.Add(new(donutSectorSmall, Arena.Center, angle, activation));
_aoes.Add(new(shape2, Arena.Center, angle, activation));
_aoes.Add(new(donutSectorSmall, pos, angle, activation));
_aoes.Add(new(shape2, pos, angle, activation));
}
}
}
Expand Down
Loading