diff --git a/BossMod/Components/ChasingAOEs.cs b/BossMod/Components/ChasingAOEs.cs index 2042737960..0ab641b311 100644 --- a/BossMod/Components/ChasingAOEs.cs +++ b/BossMod/Components/ChasingAOEs.cs @@ -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); } } @@ -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) { @@ -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(); @@ -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(); diff --git a/BossMod/Components/Exaflare.cs b/BossMod/Components/Exaflare.cs index f96eaae011..7df7dc2982 100644 --- a/BossMod/Components/Exaflare.cs +++ b/BossMod/Components/Exaflare.cs @@ -55,7 +55,7 @@ public override IEnumerable 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; } @@ -73,7 +73,7 @@ public override IEnumerable 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; diff --git a/BossMod/Components/Gaze.cs b/BossMod/Components/Gaze.cs index 209c250e67..7d8f29ea1a 100644 --- a/BossMod/Components/Gaze.cs +++ b/BossMod/Components/Gaze.cs @@ -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 @@ -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)); } } @@ -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); } @@ -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 _casters = []; + public readonly List Eyes = []; - public override IEnumerable 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 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 diff --git a/BossMod/Components/Knockback.cs b/BossMod/Components/Knockback.cs index da3b62e2bd..3bc4abdb5f 100644 --- a/BossMod/Components/Knockback.cs +++ b/BossMod/Components/Knockback.cs @@ -240,11 +240,11 @@ public override IEnumerable 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); } } diff --git a/BossMod/Components/LineOfSightAOE.cs b/BossMod/Components/LineOfSightAOE.cs index 02b4ea63c1..255b317193 100644 --- a/BossMod/Components/LineOfSightAOE.cs +++ b/BossMod/Components/LineOfSightAOE.cs @@ -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)); } } diff --git a/BossMod/Components/PersistentVoidzone.cs b/BossMod/Components/PersistentVoidzone.cs index 79fe03f3b1..58b60eaa77 100644 --- a/BossMod/Components/PersistentVoidzone.cs +++ b/BossMod/Components/PersistentVoidzone.cs @@ -14,7 +14,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) var aoes = new List(); 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; } @@ -25,7 +25,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme return; var forbidden = new List>(); 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)); } } @@ -47,11 +47,11 @@ public class PersistentVoidzoneAtCastTarget(BossModule module, float radius, Act public override IEnumerable 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() @@ -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))); } } @@ -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) diff --git a/BossMod/Components/StackSpread.cs b/BossMod/Components/StackSpread.cs index 144e1fb190..7cdfdbed59 100644 --- a/BossMod/Components/StackSpread.cs +++ b/BossMod/Components/StackSpread.cs @@ -20,7 +20,7 @@ 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; @@ -28,7 +28,7 @@ public readonly int NumInside(BossModule module) 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); } @@ -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; @@ -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!"); } @@ -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>(); 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); } @@ -193,7 +193,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var forbidden = new List>(); 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); } @@ -236,7 +236,7 @@ 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 { @@ -244,17 +244,17 @@ public override void DrawArenaForeground(int pcSlot, Actor pc) 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); } } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D041CommanderR8.cs b/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D041CommanderR8.cs index 3d80a06a28..02970f54ea 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D041CommanderR8.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D041CommanderR8.cs @@ -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)); } } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D042Protector.cs b/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D042Protector.cs index 3e214b8416..a8e1873cc2 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D042Protector.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D04Vanguard/D042Protector.cs @@ -54,7 +54,7 @@ public enum SID : uint class ArenaChanges(BossModule module) : Components.GenericAOEs(module) { - private const float Radius = 0.5f; + private const float Radius = 0.51f; // small cushion since fences don't seem to be positioned perfectly public static readonly WPos ArenaCenter = new(0f, -100f); public static readonly ArenaBoundsRect StartingBounds = new(14.5f, 22.5f); private static readonly ArenaBoundsRect defaultBounds = new(12f, 20f); @@ -231,36 +231,11 @@ class BlastCannon : Components.SimpleAOEs } class Shock(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Shock), 3f); -class HomingCannon(BossModule module) : Components.GenericAOEs(module) +class HomingCannon : Components.SimpleAOEs { - private static readonly AOEShapeRect rect = new(50, 1); - private readonly List _aoes = []; - - public override IEnumerable ActiveAOEs(int slot, Actor actor) - { - var count = _aoes.Count; - if (count == 0) - return []; - var aoes = new AOEInstance[count]; - var act0 = _aoes[0].Activation; - for (var i = 0; i < count; ++i) - { - var aoe = _aoes[i]; - aoes[i] = (aoe.Activation - act0).TotalSeconds < 1d ? aoe with { Color = Colors.Danger } : aoe with { Risky = false }; - } - return aoes; - } - - public override void OnCastStarted(Actor caster, ActorCastInfo spell) - { - if (spell.Action.ID == (uint)AID.HomingCannon) - _aoes.Add(new(rect, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); - } - - public override void OnCastFinished(Actor caster, ActorCastInfo spell) + public HomingCannon(BossModule module) : base(module, ActionID.MakeSpell(AID.HomingCannon), new AOEShapeRect(50f, 1f)) { - if (_aoes.Count != 0 && spell.Action.ID == (uint)AID.HomingCannon) - _aoes.RemoveAt(0); + MaxDangerColor = 4; } } diff --git a/BossMod/Modules/Dawntrail/Hunt/RankA/CatsEye.cs b/BossMod/Modules/Dawntrail/Hunt/RankA/CatsEye.cs index 29d567e1e9..e7937e4def 100644 --- a/BossMod/Modules/Dawntrail/Hunt/RankA/CatsEye.cs +++ b/BossMod/Modules/Dawntrail/Hunt/RankA/CatsEye.cs @@ -21,16 +21,8 @@ class CatsEye1(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeS class CatsEye2(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.CatsEye2), 40); -class CatsEye1Gaze(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.CatsEye1)) -{ - public override IEnumerable ActiveEyes(int slot, Actor actor) => _casters.Select(c => new Eye(c.CastInfo!.LocXZ, Module.CastFinishAt(c.CastInfo))); -} - -class CatsEye2Gaze(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.CatsEye2), true) -{ - public override IEnumerable ActiveEyes(int slot, Actor actor) => _casters.Select(c => new Eye(c.CastInfo!.LocXZ, Module.CastFinishAt(c.CastInfo))); -} - +class CatsEye1Gaze(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.CatsEye1)); +class CatsEye2Gaze(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.CatsEye2), true); class GravitationalWave(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.GravitationalWave), "Raidwide!"); class BloodshotGaze1(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.BloodshotGaze1)) @@ -41,7 +33,7 @@ public override IEnumerable ActiveEyes(int slot, Actor actor) var targetPosition = stackComponent?.ActiveStackTargets.FirstOrDefault()?.Position; - return _casters.Select(c => new Eye(targetPosition ?? c.Position, Module.CastFinishAt(c.CastInfo))); + return Eyes.Select(c => new Eye(targetPosition ?? c.Position, c.Activation)); } } @@ -53,7 +45,7 @@ public override IEnumerable ActiveEyes(int slot, Actor actor) var targetPosition = stackComponent?.ActiveStackTargets.FirstOrDefault()?.Position; - return _casters.Select(c => new Eye(targetPosition ?? c.Position, Module.CastFinishAt(c.CastInfo))); + return Eyes.Select(c => new Eye(targetPosition ?? c.Position, c.Activation)); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs index 4c233f2e4c..c5d5913154 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs @@ -24,9 +24,9 @@ public enum AID : uint class Thumbscrew(BossModule module) : Components.ChargeAOEs(module, ActionID.MakeSpell(AID.Thumbscrew), 4); class ThePathofLight(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.ThePathOfLight)); class GibbetCage(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.GibbetCage), 8); -class HereticsFork(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.HereticsFork), new AOEShapeCross(40, 3)); -class LightShot(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.LightShot), new AOEShapeRect(40, 2)); -class WoodenHorse(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.WoodenHorse), new AOEShapeCone(40, 45.Degrees())); +class HereticsFork(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.HereticsFork), new AOEShapeCross(40f, 3f)); +class LightShot(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.LightShot), new AOEShapeRect(40f, 2f)); +class WoodenHorse(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.WoodenHorse), new AOEShapeCone(40f, 45f.Degrees())); class Pillory(BossModule module) : Components.SingleTargetDelayableCast(module, ActionID.MakeSpell(AID.Pillory)); class D011ForgivenDissonanceStates : StateMachineBuilder diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs index 33da630133..295ed50aa4 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs @@ -38,13 +38,13 @@ class FeveredFlagellation(BossModule module) : Components.GenericBaitAway(module public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if (CurrentBaits.Count != 0 && (AID)spell.Action.ID == AID.FeveredFlagellation2) + if (CurrentBaits.Count != 0 && spell.Action.ID == (uint)AID.FeveredFlagellation2) CurrentBaits.RemoveAt(0); } public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) { - if ((IconID)iconID is >= IconID.Icon1 and <= IconID.Icon4) + if (iconID is >= (uint)IconID.Icon1 and <= (uint)IconID.Icon4) CurrentBaits.Add(new(Module.PrimaryActor, actor, rect)); } @@ -58,8 +58,8 @@ public override void Update() } } -class Exorcise(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.ExorciseA), 6, 4, 4); -class HolyWater(BossModule module) : Components.PersistentVoidzoneAtCastTarget(module, 6, ActionID.MakeSpell(AID.HolyWater), m => m.Enemies(OID.HolyWaterVoidzone).Where(z => z.EventState != 7), 0.8f); +class Exorcise(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.ExorciseA), 6f, 4, 4); +class HolyWater(BossModule module) : Components.PersistentVoidzoneAtCastTarget(module, 6f, ActionID.MakeSpell(AID.HolyWater), m => m.Enemies(OID.HolyWaterVoidzone).Where(z => z.EventState != 7), 0.8f); class D012TesleentheForgivenStates : StateMachineBuilder { diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs index c5616ae286..c15e31e502 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs @@ -62,7 +62,7 @@ class Fetters(BossModule module) : BossComponent(module) public override void Update() { - var fetters = chaintarget?.FindStatus(SID.Fetters) != null; + var fetters = chaintarget?.FindStatus((uint)SID.Fetters) != null; if (fetters) chainsactive = true; if (fetters && !chained) @@ -89,16 +89,16 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme for (var i = 0; i < hints.PotentialTargets.Count; ++i) { var e = hints.PotentialTargets[i]; - e.Priority = (OID)e.Actor.OID switch + e.Priority = e.Actor.OID switch { - OID.IronChain => 1, - OID.Boss => -1, + (uint)OID.IronChain => 1, + (uint)OID.Boss => AIHints.Enemy.PriorityInvincible, _ => 0 }; } - var ironchain = Module.Enemies(OID.IronChain).FirstOrDefault(); + var ironchain = Module.Enemies((uint)OID.IronChain).FirstOrDefault(); if (ironchain != null && !ironchain.IsDead) - hints.AddForbiddenZone(ShapeDistance.InvertedCircle(ironchain.Position, ironchain.HitboxRadius + 3)); + hints.AddForbiddenZone(ShapeDistance.InvertedCircle(ironchain.Position, 3.6f)); } public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) @@ -112,34 +112,36 @@ public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.ChainDown) + if (spell.Action.ID == (uint)AID.ChainDown) casting = false; } } class Aethersup(BossModule module) : Components.GenericAOEs(module) { - private static readonly AOEShapeCone cone = new(24, 60.Degrees()); + private static readonly AOEShapeCone cone = new(24f, 60f.Degrees()); private AOEInstance _aoe; public override IEnumerable ActiveAOEs(int slot, Actor actor) { if (_aoe != default) - yield return _aoe with { Risky = Module.Enemies(OID.IronChain).Any(x => x.IsDead) }; + return [_aoe with { Risky = Module.Enemies((uint)OID.IronChain).Any(x => x.IsDead) }]; + else + return []; } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.AethersupFirst) + if (spell.Action.ID == (uint)AID.AethersupFirst) _aoe = new(cone, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell)); } public override void OnEventCast(Actor caster, ActorCastEvent spell) { - switch ((AID)spell.Action.ID) + switch (spell.Action.ID) { - case AID.AethersupFirst: - case AID.AethersupRest: + case (uint)AID.AethersupFirst: + case (uint)AID.AethersupRest: if (++NumCasts == 4) { _aoe = default; @@ -167,13 +169,13 @@ public override void AddHints(int slot, Actor actor, TextHints hints) } } -class PendulumAOE(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.PendulumAOE3), 15); +class PendulumAOE(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.PendulumAOE3), 15f); -class Knout(BossModule module, AID aid) : Components.SimpleAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCone(24, 105.Degrees())); +class Knout(BossModule module, AID aid) : Components.SimpleAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCone(24f, 105f.Degrees())); class LeftKnout(BossModule module) : Knout(module, AID.LeftKnout); class RightKnout(BossModule module) : Knout(module, AID.RightKnout); -class Taphephobia(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.Taphephobia2), 6); +class Taphephobia(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.Taphephobia2), 6f); class IntoTheLight(BossModule module) : Components.LineStack(module, ActionID.MakeSpell(AID.IntoTheLightMarker), ActionID.MakeSpell(AID.IntoTheLight), 5.3f); @@ -183,24 +185,24 @@ class CatONineTails(BossModule module) : Components.GenericRotatingAOE(module) public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.FierceBeating1) - Sequences.Add(new(_shape, D013Philia.ArenaCenter, spell.Rotation + 180.Degrees(), -45.Degrees(), Module.CastFinishAt(spell), 2, 8)); + if (spell.Action.ID == (uint)AID.FierceBeating1) + Sequences.Add(new(_shape, spell.LocXZ, spell.Rotation + 180f.Degrees(), -45f.Degrees(), Module.CastFinishAt(spell), 2, 8)); } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.CatONineTails) + if (spell.Action.ID == (uint)AID.CatONineTails) AdvanceSequence(0, WorldState.CurrentTime); } } -class FierceBeating(BossModule module) : Components.Exaflare(module, 4) +class FierceBeating(BossModule module) : Components.Exaflare(module, 4f) { private readonly List _casters = []; private int linesstartedcounttotal; private int linesstartedcount1; private int linesstartedcount2; - private static readonly AOEShapeCircle circle = new(4); + private static readonly AOEShapeCircle circle = new(4f); private DateTime _activation; public override IEnumerable ActiveAOEs(int slot, Actor actor) @@ -227,9 +229,9 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) aoes[index++] = new(Shape, aoe.Item1, aoe.Item3, aoe.Item2, ImminentColor); } if (linesstartedcount1 < 8) - aoes[index++] = new(circle, WPos.RotateAroundOrigin(linesstartedcount1 * 45, D013Philia.ArenaCenter, _casters[0]), default, _activation.AddSeconds(linesstartedcount1 * 3.7f)); + aoes[index++] = new(circle, WPos.ClampToGrid(WPos.RotateAroundOrigin(linesstartedcount1 * 45, D013Philia.ArenaCenter, _casters[0])), default, _activation.AddSeconds(linesstartedcount1 * 3.7d)); if (linesCount > 1 && linesstartedcount2 < 8) - aoes[index++] = new(circle, WPos.RotateAroundOrigin(linesstartedcount2 * 45, D013Philia.ArenaCenter, _casters[1]), default, _activation.AddSeconds(linesstartedcount2 * 3.7f)); + aoes[index++] = new(circle, WPos.ClampToGrid(WPos.RotateAroundOrigin(linesstartedcount2 * 45, D013Philia.ArenaCenter, _casters[1])), default, _activation.AddSeconds(linesstartedcount2 * 3.7d)); return aoes[..index]; } @@ -246,9 +248,9 @@ public override void Update() public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.FierceBeating4) + if (spell.Action.ID == (uint)AID.FierceBeating4) { - Lines.Add(new() { Next = caster.Position, Advance = 2.5f * spell.Rotation.ToDirection(), NextExplosion = Module.CastFinishAt(spell), TimeToMove = 1, ExplosionsLeft = 7, MaxShownExplosions = 3 }); + Lines.Add(new() { Next = spell.LocXZ, Advance = 2.5f * spell.Rotation.ToDirection(), NextExplosion = Module.CastFinishAt(spell), TimeToMove = 1f, ExplosionsLeft = 7, MaxShownExplosions = 3 }); _activation = Module.CastFinishAt(spell); ++linesstartedcounttotal; ++NumCasts; @@ -262,9 +264,9 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if ((AID)spell.Action.ID == AID.FierceBeating6) + if (spell.Action.ID == (uint)AID.FierceBeating6) { - Lines.Add(new() { Next = caster.Position, Advance = 2.5f * caster.Rotation.ToDirection(), NextExplosion = WorldState.FutureTime(1), TimeToMove = 1, ExplosionsLeft = 7, MaxShownExplosions = 3 }); + Lines.Add(new() { Next = caster.Position, Advance = 2.5f * caster.Rotation.ToDirection(), NextExplosion = WorldState.FutureTime(1), TimeToMove = 1f, ExplosionsLeft = 7, MaxShownExplosions = 3 }); ++linesstartedcounttotal; if (linesstartedcounttotal % 2 != 0) ++linesstartedcount1; @@ -273,16 +275,16 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) } if (Lines.Count > 0) { - if ((AID)spell.Action.ID is AID.FierceBeating4 or AID.FierceBeating6) + if (spell.Action.ID is (uint)AID.FierceBeating4 or (uint)AID.FierceBeating6) { - var index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1)); + var index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1f)); AdvanceLine(Lines[index], caster.Position); if (Lines[index].ExplosionsLeft == 0) Lines.RemoveAt(index); } - else if ((AID)spell.Action.ID == AID.FierceBeating5) + else if (spell.Action.ID == (uint)AID.FierceBeating5) { - var index = Lines.FindIndex(item => item.Next.AlmostEqual(spell.TargetXZ, 1)); + var index = Lines.FindIndex(item => item.Next.AlmostEqual(spell.TargetXZ, 1f)); AdvanceLine(Lines[index], spell.TargetXZ); if (Lines[index].ExplosionsLeft == 0) Lines.RemoveAt(index); diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs b/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs index 38f0a6cf0c..74988e6573 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs @@ -88,7 +88,7 @@ public override void OnActorEAnim(Actor actor, uint state) if (actor.Rotation.AlmostEqual(rotation, Angle.DegToRad)) { for (var i = 0; i < positions.Length; ++i) - _aoes.Add(new(circle, positions[i], default, activation)); + _aoes.Add(new(circle, WPos.ClampToGrid(positions[i]), default, activation)); break; } } diff --git a/BossMod/Replay/Visualization/OpList.cs b/BossMod/Replay/Visualization/OpList.cs index 6636381db3..dcaf6d4635 100644 --- a/BossMod/Replay/Visualization/OpList.cs +++ b/BossMod/Replay/Visualization/OpList.cs @@ -136,6 +136,7 @@ private bool FilterOp(WorldState.Operation o) ClientState.OpAnimationLockChange => false, ClientState.OpComboChange => false, ClientState.OpCooldown => false, + ClientState.OpForcedMovementDirectionChange => false, NetworkState.OpServerIPC => false, _ => true }; diff --git a/BossMod/Util/Polygon.cs b/BossMod/Util/Polygon.cs index fa53177009..303023cff5 100644 --- a/BossMod/Util/Polygon.cs +++ b/BossMod/Util/Polygon.cs @@ -106,9 +106,10 @@ public bool Contains(WDir p) var holeTasks = new Task[holecount]; for (var i = 0; i < holecount; ++i) { + var index = i; holeTasks[i] = Task.Run(() => { - holeEdgeBuckets[i] = BuildEdgeBucketsForContour(Interior(i)); + holeEdgeBuckets[index] = BuildEdgeBucketsForContour(Interior(index)); }); } Task.WaitAll(holeTasks); diff --git a/BossMod/Util/WPosDir.cs b/BossMod/Util/WPosDir.cs index 9fbe41295e..b984b4c509 100644 --- a/BossMod/Util/WPosDir.cs +++ b/BossMod/Util/WPosDir.cs @@ -99,7 +99,13 @@ public WPos(Vector2 v) : this(v.X, v.Y) { } public readonly WPos Rounded() => new(MathF.Round(X), MathF.Round(Z)); public readonly WPos Rounded(float precision) => Scaled(1f / precision).Rounded().Scaled(precision); public static WPos Lerp(WPos from, WPos to, float progress) => new(from.ToVec2() * (1f - progress) + to.ToVec2() * progress); - + public static WPos ClampToGrid(WPos coord) // AOEs are getting clamped to a grid, if spell.LocXZ can't be used, you can correct the position with this method + { + const float gridSize = (float)(2000.0d / 65535.0d); + const float gridSizeInv = (float)(1d / (2000.0d / 65535.0d)); + int gridIndexX = (int)MathF.Round(coord.X * gridSizeInv), gridIndexZ = (int)MathF.Round(coord.Z * gridSizeInv); + return new((gridIndexX - 0.5f) * gridSize, (gridIndexZ - 0.5f) * gridSize); + } public static WPos RotateAroundOrigin(float rotateByDegrees, WPos origin, WPos point) { var (sin, cos) = ((float, float))Math.SinCos(rotateByDegrees * Angle.DegToRad);