diff --git a/BossMod/BossModule/AIHintsVisualizer.cs b/BossMod/BossModule/AIHintsVisualizer.cs index 11d5548ed0..cda35dff25 100644 --- a/BossMod/BossModule/AIHintsVisualizer.cs +++ b/BossMod/BossModule/AIHintsVisualizer.cs @@ -54,7 +54,7 @@ private MapVisualizer BuildZoneVisualizer(Func shape) { var map = new Map(); hints.InitPathfindMap(map); - map.BlockPixelsInside(shape, 0, NavigationDecision.DefaultForbiddenZoneCushion); + map.BlockPixelsInside(shape, 0, map.Resolution * NavigationDecision.DefaultForbiddenZoneCushion); return new MapVisualizer(map, 0, player.Position); } @@ -68,8 +68,9 @@ private MapVisualizer BuildPathfindingVisualizer() _navi.Map = new(); hints.PathfindMapBounds.PathfindMap(_navi.Map, hints.PathfindMapCenter); var imm = NavigationDecision.ImminentExplosionTime(ws.CurrentTime); + var cushion = _navi.Map.Resolution * NavigationDecision.DefaultForbiddenZoneCushion; foreach (var (shape, activation) in hints.ForbiddenZones) - NavigationDecision.AddBlockerZone(_navi.Map, imm, activation, shape, NavigationDecision.DefaultForbiddenZoneCushion); + NavigationDecision.AddBlockerZone(_navi.Map, imm, activation, shape, cushion); if (targetEnemy != null) _navi.MapGoal = NavigationDecision.AddTargetGoal(_navi.Map, targetEnemy.Actor.Position, targetEnemy.Actor.HitboxRadius + player.HitboxRadius + targeting.range, targetEnemy.Actor.Rotation, targeting.pos, 0); } diff --git a/BossMod/BossModule/BossModule.cs b/BossMod/BossModule/BossModule.cs index 55d0960170..2dad51035d 100644 --- a/BossMod/BossModule/BossModule.cs +++ b/BossMod/BossModule/BossModule.cs @@ -50,7 +50,18 @@ public List Enemies(ReadOnlySpan enemies) // component management: at most one component of any given type can be active at any time public readonly List Components = []; - public T? FindComponent() where T : BossComponent => Components.OfType().FirstOrDefault(); + public T? FindComponent() where T : BossComponent + { + var count = Components.Count; + for (var i = 0; i < count; ++i) + { + if (Components[i] is T matchingComponent) + { + return matchingComponent; + } + } + return null; + } private int componentCount; public void ActivateComponent() where T : BossComponent diff --git a/BossMod/Data/ActorState.cs b/BossMod/Data/ActorState.cs index 41ff026400..0732922050 100644 --- a/BossMod/Data/ActorState.cs +++ b/BossMod/Data/ActorState.cs @@ -15,21 +15,20 @@ public sealed class ActorState : IEnumerable // in addition to worldstate's modification event, extra event with actor pointer is dispatched for all actor events public abstract record class Operation(ulong InstanceID) : WorldState.Operation { - protected abstract void ExecActor(WorldState ws, Actor actor); - protected override void Exec(WorldState ws) + protected abstract void ExecActor(ref WorldState ws, ref Actor actor); + protected override void Exec(ref WorldState ws) { if (ws.Actors.Actors.TryGetValue(InstanceID, out var actor)) - ExecActor(ws, actor); + ExecActor(ref ws, ref actor); } } public List CompareToInitial() { List ops = new(Actors.Count * 5); - foreach (var act in Actors.Values) { - var instanceID = act.InstanceID; + ref var instanceID = ref act.InstanceID; ops.Add(new OpCreate(instanceID, act.OID, act.SpawnIndex, act.Name, act.NameID, act.Type, act.Class, act.Level, act.PosRot, act.HitboxRadius, act.HPMP, act.IsTargetable, act.IsAlly, act.OwnerID, act.FateID)); if (act.IsDead) ops.Add(new OpDead(instanceID, true)); @@ -47,15 +46,20 @@ public List CompareToInitial() ops.Add(new OpTether(instanceID, act.Tether)); if (act.CastInfo != null) ops.Add(new OpCastInfo(instanceID, act.CastInfo)); - for (var j = 0; j < act.Statuses.Length; ++j) + var statuslen = act.Statuses.Length; + for (var i = 0; i < statuslen; ++i) { - var status = act.Statuses[j]; + ref var status = ref act.Statuses[i]; if (status.ID != 0) - ops.Add(new OpStatus(instanceID, j, status)); + ops.Add(new OpStatus(instanceID, i, status)); + } + var effectlen = act.IncomingEffects.Length; + for (var i = 0; i < effectlen; ++i) + { + ref var effect = ref act.IncomingEffects[i]; + if (effect.GlobalSequence != 0) + ops.Add(new OpIncomingEffect(act.InstanceID, i, effect)); } - for (var i = 0; i < act.IncomingEffects.Length; ++i) - if (act.IncomingEffects[i].GlobalSequence != 0) - ops.Add(new OpIncomingEffect(act.InstanceID, i, act.IncomingEffects[i])); } return ops; } @@ -66,22 +70,26 @@ public void Tick(in FrameState frame) foreach (var act in Actors.Values) { act.PrevPosRot = act.PosRot; - if (act.CastInfo != null) - act.CastInfo.ElapsedTime = Math.Min(act.CastInfo.ElapsedTime + frame.Duration, act.CastInfo.AdjustedTotalTime); + ref var castinfo = ref act.CastInfo; + if (castinfo != null) + castinfo.ElapsedTime = Math.Min(castinfo.ElapsedTime + frame.Duration, castinfo.AdjustedTotalTime); RemovePendingEffects(act, (in PendingEffect p) => p.Expiration < ts); } } - private void AddPendingEffects(Actor source, ActorCastEvent ev, DateTime timestamp) + private void AddPendingEffects(ref Actor source, ActorCastEvent ev, DateTime timestamp) { var expiration = timestamp.AddSeconds(3); - for (int i = 0; i < ev.Targets.Count; ++i) + var count = ev.Targets.Count; + for (var i = 0; i < count; ++i) { - var target = ev.Targets[i].ID == source.InstanceID ? source : Find(ev.Targets[i].ID); // most common case by far is self-target + var targeti = ev.Targets[i]; + var targetID = targeti.ID; + var target = targetID == source.InstanceID ? source : Find(targetID); // most common case by far is self-target if (target == null) continue; - foreach (var eff in ev.Targets[i].Effects) + foreach (var eff in targeti.Effects) { var effSource = eff.FromTarget ? target : source; var effTarget = eff.AtSource ? source : target; @@ -134,8 +142,8 @@ public sealed record class OpCreate(ulong InstanceID, uint OID, int SpawnIndex, ActorHPMP HPMP, bool IsTargetable, bool IsAlly, ulong OwnerID, uint FateID) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) { } - protected override void Exec(WorldState ws) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { } + protected override void Exec(ref WorldState ws) { var actor = ws.Actors.Actors[InstanceID] = new Actor(InstanceID, OID, SpawnIndex, Name, NameID, Type, Class, Level, PosRot, HitboxRadius, HPMP, IsTargetable, IsAlly, OwnerID, FateID); ws.Actors.Added.Fire(actor); @@ -165,34 +173,37 @@ public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("A public Event Removed = new(); public sealed record class OpDestroy(ulong InstanceID) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.IsDestroyed = true; + var wsactors = ws.Actors; if (actor.InCombat) // exit combat { actor.InCombat = false; - ws.Actors.InCombatChanged.Fire(actor); + wsactors.InCombatChanged.Fire(actor); } if (actor.Tether.Target != 0) // untether { - ws.Actors.Untethered.Fire(actor); + wsactors.Untethered.Fire(actor); actor.Tether = default; } if (actor.CastInfo != null) // stop casting { - ws.Actors.CastFinished.Fire(actor); + wsactors.CastFinished.Fire(actor); actor.CastInfo = null; } - for (var i = 0; i < actor.Statuses.Length; ++i) + var len = actor.Statuses.Length; + for (var i = 0; i < len; ++i) { - if (actor.Statuses[i].ID != 0) // clear statuses + ref var status = ref actor.Statuses[i]; + if (status.ID != 0) // clear statuses { - ws.Actors.StatusLose.Fire(actor, i); - actor.Statuses[i] = default; + wsactors.StatusLose.Fire(actor, i); + status = default; } } - ws.Actors.Removed.Fire(actor); - ws.Actors.Actors.Remove(InstanceID); + wsactors.Removed.Fire(actor); + wsactors.Actors.Remove(InstanceID); } public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("ACT-"u8).Emit(InstanceID, "X8"); } @@ -200,7 +211,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event Renamed = new(); public sealed record class OpRename(ulong InstanceID, string Name, uint NameID) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.Name = Name; actor.NameID = NameID; @@ -212,7 +223,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event ClassChanged = new(); public sealed record class OpClassChange(ulong InstanceID, Class Class, int Level) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.Class = Class; actor.Level = Level; @@ -224,7 +235,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event Moved = new(); public sealed record class OpMove(ulong InstanceID, Vector4 PosRot) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.PosRot = PosRot; ws.Actors.Moved.Fire(actor); @@ -235,7 +246,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event SizeChanged = new(); public sealed record class OpSizeChange(ulong InstanceID, float HitboxRadius) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.HitboxRadius = HitboxRadius; ws.Actors.SizeChanged.Fire(actor); @@ -246,7 +257,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event HPMPChanged = new(); public sealed record class OpHPMP(ulong InstanceID, ActorHPMP HPMP) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.HPMP = HPMP; ws.Actors.HPMPChanged.Fire(actor); @@ -257,7 +268,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event IsTargetableChanged = new(); public sealed record class OpTargetable(ulong InstanceID, bool Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.IsTargetable = Value; ws.Actors.IsTargetableChanged.Fire(actor); @@ -268,7 +279,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event IsAllyChanged = new(); public sealed record class OpAlly(ulong InstanceID, bool Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.IsAlly = Value; ws.Actors.IsAllyChanged.Fire(actor); @@ -279,7 +290,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event IsDeadChanged = new(); public sealed record class OpDead(ulong InstanceID, bool Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.IsDead = Value; ws.Actors.IsDeadChanged.Fire(actor); @@ -290,7 +301,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event InCombatChanged = new(); public sealed record class OpCombat(ulong InstanceID, bool Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.InCombat = Value; ws.Actors.InCombatChanged.Fire(actor); @@ -301,7 +312,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event AggroPlayerChanged = new(); public sealed record class OpAggroPlayer(ulong InstanceID, bool Has) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.AggroPlayer = Has; ws.Actors.AggroPlayerChanged.Fire(actor); @@ -312,7 +323,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event ModelStateChanged = new(); public sealed record class OpModelState(ulong InstanceID, ActorModelState Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.ModelState = Value; ws.Actors.ModelStateChanged.Fire(actor); @@ -323,7 +334,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event EventStateChanged = new(); public sealed record class OpEventState(ulong InstanceID, byte Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.EventState = Value; ws.Actors.EventStateChanged.Fire(actor); @@ -334,7 +345,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event TargetChanged = new(); public sealed record class OpTarget(ulong InstanceID, ulong Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.TargetID = Value; ws.Actors.TargetChanged.Fire(actor); @@ -345,7 +356,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event MountChanged = new(); public sealed record class OpMount(ulong InstanceID, uint Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.MountId = Value; ws.Actors.MountChanged.Fire(actor); @@ -358,7 +369,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event Untethered = new(); // note that actor structure still contains previous tether info when this is invoked; invoked if actor disappears without untethering public sealed record class OpTether(ulong InstanceID, ActorTetherInfo Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { if (actor.Tether.Target != 0) ws.Actors.Untethered.Fire(actor); @@ -373,13 +384,15 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event CastFinished = new(); // note that actor structure still contains cast details when this is invoked; invoked if actor disappears without finishing cast public sealed record class OpCastInfo(ulong InstanceID, ActorCastInfo? Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { - if (actor.CastInfo != null) - ws.Actors.CastFinished.Fire(actor); - actor.CastInfo = Value != null ? Value with { } : null; + ref var castinfo = ref actor.CastInfo; + var wsactors = ws.Actors; + if (castinfo != null) + wsactors.CastFinished.Fire(actor); + castinfo = Value != null ? Value with { } : null; if (Value != null) - ws.Actors.CastStarted.Fire(actor); + wsactors.CastStarted.Fire(actor); } public override void Write(ReplayRecorder.Output output) { @@ -394,12 +407,14 @@ public override void Write(ReplayRecorder.Output output) public Event CastEvent = new(); public sealed record class OpCastEvent(ulong InstanceID, ActorCastEvent Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { - if (actor.CastInfo?.Action == Value.Action) - actor.CastInfo.EventHappened = true; - ws.Actors.AddPendingEffects(actor, Value, ws.CurrentTime); - ws.Actors.CastEvent.Fire(actor, Value); + ref var castinfo = ref actor.CastInfo; + var wsactors = ws.Actors; + if (castinfo?.Action == Value.Action) + castinfo.EventHappened = true; + wsactors.AddPendingEffects(ref actor, Value, ws.CurrentTime); + wsactors.CastEvent.Fire(actor, Value); } public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("CST!"u8) .EmitActor(InstanceID) @@ -418,7 +433,7 @@ public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("C public Event EffectResult = new(); public sealed record class OpEffectResult(ulong InstanceID, uint Seq, int TargetIndex) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { ws.Actors.RemovePendingEffects(actor, (in PendingEffect p) => p.GlobalSequence == Seq && p.TargetIndex == TargetIndex); ws.Actors.EffectResult.Fire(actor, Seq, TargetIndex); @@ -430,15 +445,16 @@ protected override void ExecActor(WorldState ws, Actor actor) public Event StatusLose = new(); // note that status structure still contains details when this is invoked; invoked if actor disappears public sealed record class OpStatus(ulong InstanceID, int Index, ActorStatus Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { ref var prev = ref actor.Statuses[Index]; + var wsactors = ws.Actors; if (prev.ID != 0 && (prev.ID != Value.ID || prev.SourceID != Value.SourceID)) - ws.Actors.StatusLose.Fire(actor, Index); + wsactors.StatusLose.Fire(actor, Index); actor.Statuses[Index] = Value; actor.PendingStatuses.RemoveAll(s => s.StatusId == Value.ID && s.Effect.SourceInstanceId == Value.SourceID); if (Value.ID != 0) - ws.Actors.StatusGain.Fire(actor, Index); + wsactors.StatusGain.Fire(actor, Index); } public override void Write(ReplayRecorder.Output output) { @@ -453,19 +469,19 @@ public override void Write(ReplayRecorder.Output output) public Event IncomingEffectRemove = new(); public sealed record class OpIncomingEffect(ulong InstanceID, int Index, ActorIncomingEffect Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { ref var prev = ref actor.IncomingEffects[Index]; if (prev.GlobalSequence != 0 && (prev.GlobalSequence != Value.GlobalSequence || prev.TargetIndex != Value.TargetIndex)) { - if (prev.Effects.Any(eff => eff.Type is ActionEffectType.Knockback or ActionEffectType.Attract1 or ActionEffectType.Attract2 or ActionEffectType.AttractCustom1 or ActionEffectType.AttractCustom2 or ActionEffectType.AttractCustom3)) + if (prev.Effects.Any(eff => eff.Type is >= ActionEffectType.Knockback and <= ActionEffectType.AttractCustom3)) --actor.PendingKnockbacks; ws.Actors.IncomingEffectRemove.Fire(actor, Index); } actor.IncomingEffects[Index] = Value; if (Value.GlobalSequence != 0) { - if (Value.Effects.Any(eff => eff.Type is ActionEffectType.Knockback or ActionEffectType.Attract1 or ActionEffectType.Attract2 or ActionEffectType.AttractCustom1 or ActionEffectType.AttractCustom2 or ActionEffectType.AttractCustom3)) + if (prev.Effects.Any(eff => eff.Type is >= ActionEffectType.Knockback and <= ActionEffectType.AttractCustom3)) ++actor.PendingKnockbacks; ws.Actors.IncomingEffectAdd.Fire(actor, Index); } @@ -483,7 +499,7 @@ public override void Write(ReplayRecorder.Output output) public Event IconAppeared = new(); public sealed record class OpIcon(ulong InstanceID, uint IconID, ulong TargetID) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) => ws.Actors.IconAppeared.Fire(actor, IconID, TargetID); + protected override void ExecActor(ref WorldState ws, ref Actor actor) => ws.Actors.IconAppeared.Fire(actor, IconID, TargetID); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("ICON"u8).EmitActor(InstanceID).Emit(IconID).EmitActor(TargetID); } @@ -491,7 +507,7 @@ public sealed record class OpIcon(ulong InstanceID, uint IconID, ulong TargetID) public Event EventObjectStateChange = new(); public sealed record class OpEventObjectStateChange(ulong InstanceID, ushort State) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) => ws.Actors.EventObjectStateChange.Fire(actor, State); + protected override void ExecActor(ref WorldState ws, ref Actor actor) => ws.Actors.EventObjectStateChange.Fire(actor, State); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("ESTA"u8).EmitActor(InstanceID).Emit(State, "X4"); } @@ -499,7 +515,7 @@ public sealed record class OpEventObjectStateChange(ulong InstanceID, ushort Sta public Event EventObjectAnimation = new(); public sealed record class OpEventObjectAnimation(ulong InstanceID, ushort Param1, ushort Param2) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) => ws.Actors.EventObjectAnimation.Fire(actor, Param1, Param2); + protected override void ExecActor(ref WorldState ws, ref Actor actor) => ws.Actors.EventObjectAnimation.Fire(actor, Param1, Param2); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("EANM"u8).EmitActor(InstanceID).Emit(Param1, "X4").Emit(Param2, "X4"); } @@ -507,21 +523,21 @@ public sealed record class OpEventObjectAnimation(ulong InstanceID, ushort Param public Event PlayActionTimelineEvent = new(); public sealed record class OpPlayActionTimelineEvent(ulong InstanceID, ushort ActionTimelineID) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) => ws.Actors.PlayActionTimelineEvent.Fire(actor, ActionTimelineID); + protected override void ExecActor(ref WorldState ws, ref Actor actor) => ws.Actors.PlayActionTimelineEvent.Fire(actor, ActionTimelineID); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("PATE"u8).EmitActor(InstanceID).Emit(ActionTimelineID, "X4"); } public Event EventNpcYell = new(); public sealed record class OpEventNpcYell(ulong InstanceID, ushort Message) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) => ws.Actors.EventNpcYell.Fire(actor, Message); + protected override void ExecActor(ref WorldState ws, ref Actor actor) => ws.Actors.EventNpcYell.Fire(actor, Message); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("NYEL"u8).EmitActor(InstanceID).Emit(Message); } public Event EventOpenTreasure = new(); public sealed record class OpEventOpenTreasure(ulong InstanceID) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) => ws.Actors.EventOpenTreasure.Fire(actor); + protected override void ExecActor(ref WorldState ws, ref Actor actor) => ws.Actors.EventOpenTreasure.Fire(actor); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("OPNT"u8).EmitActor(InstanceID); } } diff --git a/BossMod/Data/ClientState.cs b/BossMod/Data/ClientState.cs index 747e58b0f7..174c554aa9 100644 --- a/BossMod/Data/ClientState.cs +++ b/BossMod/Data/ClientState.cs @@ -193,7 +193,7 @@ public void Tick(float dt) public Event ActionRequested = new(); public sealed record class OpActionRequest(ClientActionRequest Request) : WorldState.Operation { - protected override void Exec(WorldState ws) => ws.Client.ActionRequested.Fire(this); + protected override void Exec(ref WorldState ws) => ws.Client.ActionRequested.Fire(this); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("CLAR"u8) .Emit(Request.Action) .EmitActor(Request.TargetID) @@ -207,7 +207,7 @@ public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("C public Event ActionRejected = new(); public sealed record class OpActionReject(ClientActionReject Value) : WorldState.Operation { - protected override void Exec(WorldState ws) => ws.Client.ActionRejected.Fire(this); + protected override void Exec(ref WorldState ws) => ws.Client.ActionRejected.Fire(this); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("CLRJ"u8) .Emit(Value.Action) .Emit(Value.SourceSequence) @@ -218,7 +218,7 @@ public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("C public Event CountdownChanged = new(); public sealed record class OpCountdownChange(float? Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.CountdownRemaining = Value; ws.Client.CountdownChanged.Fire(this); @@ -235,7 +235,7 @@ public override void Write(ReplayRecorder.Output output) public Event AnimationLockChanged = new(); public sealed record class OpAnimationLockChange(float Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.AnimationLock = Value; ws.Client.AnimationLockChanged.Fire(this); @@ -246,7 +246,7 @@ protected override void Exec(WorldState ws) public Event ComboChanged = new(); public sealed record class OpComboChange(Combo Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.ComboState = Value; ws.Client.ComboChanged.Fire(this); @@ -257,7 +257,7 @@ protected override void Exec(WorldState ws) public Event PlayerStatsChanged = new(); public sealed record class OpPlayerStatsChange(Stats Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.PlayerStats = Value; ws.Client.PlayerStatsChanged.Fire(this); @@ -268,12 +268,12 @@ protected override void Exec(WorldState ws) public Event CooldownsChanged = new(); public sealed record class OpCooldown(bool Reset, List<(int group, Cooldown value)> Cooldowns) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { if (Reset) Array.Fill(ws.Client.Cooldowns, default); - - for (var i = 0; i < Cooldowns.Count; ++i) + var count = Cooldowns.Count; + for (var i = 0; i < count; ++i) { var cd = Cooldowns[i]; ws.Client.Cooldowns[cd.group] = cd.value; @@ -297,7 +297,7 @@ public override void Write(ReplayRecorder.Output output) public Event DutyActionsChanged = new(); public sealed record class OpDutyActionsChange(DutyAction Slot0, DutyAction Slot1) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.DutyActions[0] = Slot0; ws.Client.DutyActions[1] = Slot1; @@ -309,7 +309,7 @@ protected override void Exec(WorldState ws) public Event BozjaHolsterChanged = new(); public sealed record class OpBozjaHolsterChange(List<(BozjaHolsterID entry, byte count)> Contents) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { Array.Fill(ws.Client.BozjaHolster, (byte)0); for (var i = 0; i < Contents.Count; ++i) @@ -337,7 +337,7 @@ public sealed record class OpBlueMageSpellsChange(uint[] Values) : WorldState.Op { public readonly uint[] Values = Values; - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { Array.Copy(Values, ws.Client.BlueMageSpells, NumBlueMageSpells); ws.Client.BlueMageSpellsChanged.Fire(this); @@ -359,7 +359,7 @@ public sealed record class OpClassJobLevelsChange(short[] Values) : WorldState.O { public readonly short[] Values = Values; - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { Array.Fill(ws.Client.ClassJobLevels, (short)0); for (var i = 0; i < Values.Length; ++i) @@ -381,7 +381,7 @@ public override void Write(ReplayRecorder.Output output) public Event ActiveFateChanged = new(); public sealed record class OpActiveFateChange(Fate Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.ActiveFate = Value; ws.Client.ActiveFateChanged.Fire(this); @@ -392,7 +392,7 @@ protected override void Exec(WorldState ws) public Event ActivePetChanged = new(); public sealed record class OpActivePetChange(Pet Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.ActivePet = Value; ws.Client.ActivePetChanged.Fire(this); @@ -403,7 +403,7 @@ protected override void Exec(WorldState ws) public Event FocusTargetChanged = new(); public sealed record class OpFocusTargetChange(ulong Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Client.FocusTargetId = Value; ws.Client.FocusTargetChanged.Fire(this); diff --git a/BossMod/Data/NetworkState.cs b/BossMod/Data/NetworkState.cs index c8fec330f9..682a025fb3 100644 --- a/BossMod/Data/NetworkState.cs +++ b/BossMod/Data/NetworkState.cs @@ -17,7 +17,7 @@ public readonly record struct ServerIPC(Network.ServerIPC.PacketID ID, ushort Op public Event IDScrambleChanged = new(); public sealed record class OpIDScramble(uint Value) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Network.IDScramble = Value; ws.Network.IDScrambleChanged.Fire(this); @@ -28,7 +28,7 @@ protected override void Exec(WorldState ws) public Event ServerIPCReceived = new(); public sealed record class OpServerIPC(ServerIPC Packet) : WorldState.Operation { - protected override void Exec(WorldState ws) => ws.Network.ServerIPCReceived.Fire(this); + protected override void Exec(ref WorldState ws) => ws.Network.ServerIPCReceived.Fire(this); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("IPCS"u8) .Emit((int)Packet.ID) .Emit(Packet.Opcode) diff --git a/BossMod/Data/PartyState.cs b/BossMod/Data/PartyState.cs index 0c155b2b97..ae95b9700b 100644 --- a/BossMod/Data/PartyState.cs +++ b/BossMod/Data/PartyState.cs @@ -157,7 +157,7 @@ public int FindSlot(ReadOnlySpan name, StringComparison cmp = StringCompar public Event Modified = new(); public sealed record class OpModify(int Slot, Member Member) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { if (Slot >= 0 && Slot < ws.Party.Members.Length) { @@ -176,7 +176,7 @@ protected override void Exec(WorldState ws) public Event LimitBreakChanged = new(); public sealed record class OpLimitBreakChange(int Cur, int Max) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Party.LimitBreakCur = Cur; ws.Party.LimitBreakMax = Max; diff --git a/BossMod/Data/WaymarkState.cs b/BossMod/Data/WaymarkState.cs index 5d5805a51b..7b4cddaacf 100644 --- a/BossMod/Data/WaymarkState.cs +++ b/BossMod/Data/WaymarkState.cs @@ -39,7 +39,7 @@ public Vector3? this[Waymark wm] public Event Changed = new(); public sealed record class OpWaymarkChange(Waymark ID, Vector3? Pos) : WorldState.Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Waymarks[ID] = Pos; ws.Waymarks.Changed.Fire(this); diff --git a/BossMod/Data/WorldState.cs b/BossMod/Data/WorldState.cs index 689009b253..9f82612a42 100644 --- a/BossMod/Data/WorldState.cs +++ b/BossMod/Data/WorldState.cs @@ -36,11 +36,11 @@ public abstract record class Operation internal void Execute(WorldState ws) { - Exec(ws); + Exec(ref ws); Timestamp = ws.CurrentTime; } - protected abstract void Exec(WorldState ws); + protected abstract void Exec(ref WorldState ws); public abstract void Write(ReplayRecorder.Output output); } @@ -78,7 +78,7 @@ public List CompareToInitial() public Event FrameStarted = new(); public sealed record class OpFrameStart(FrameState Frame, TimeSpan PrevUpdateTime, ClientState.Gauge GaugePayload, Angle CameraAzimuth) : Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.Frame = Frame; ws.Client.CameraAzimuth = CameraAzimuth; @@ -103,14 +103,14 @@ public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("F public Event UserMarkerAdded = new(); public sealed record class OpUserMarker(string Text) : Operation { - protected override void Exec(WorldState ws) => ws.UserMarkerAdded.Fire(this); + protected override void Exec(ref WorldState ws) => ws.UserMarkerAdded.Fire(this); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("UMRK"u8).Emit(Text); } public Event RSVDataReceived = new(); public sealed record class OpRSVData(string Key, string Value) : Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { Service.LuminaRSV[Key] = Encoding.UTF8.GetBytes(Value); // TODO: reconsider... ws.RSVEntries[Key] = Value; @@ -122,7 +122,7 @@ protected override void Exec(WorldState ws) public Event CurrentZoneChanged = new(); public sealed record class OpZoneChange(ushort Zone, ushort CFCID) : Operation { - protected override void Exec(WorldState ws) + protected override void Exec(ref WorldState ws) { ws.CurrentZone = Zone; ws.CurrentCFCID = CFCID; @@ -135,14 +135,14 @@ protected override void Exec(WorldState ws) public Event DirectorUpdate = new(); public sealed record class OpDirectorUpdate(uint DirectorID, uint UpdateID, uint Param1, uint Param2, uint Param3, uint Param4) : Operation { - protected override void Exec(WorldState ws) => ws.DirectorUpdate.Fire(this); + protected override void Exec(ref WorldState ws) => ws.DirectorUpdate.Fire(this); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("DIRU"u8).Emit(DirectorID, "X8").Emit(UpdateID, "X8").Emit(Param1, "X8").Emit(Param2, "X8").Emit(Param3, "X8").Emit(Param4, "X8"); } public Event EnvControl = new(); public sealed record class OpEnvControl(byte Index, uint State) : Operation { - protected override void Exec(WorldState ws) => ws.EnvControl.Fire(this); + protected override void Exec(ref WorldState ws) => ws.EnvControl.Fire(this); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("ENVC"u8).Emit(Index, "X2").Emit(State, "X8"); } @@ -151,7 +151,7 @@ public sealed record class OpSystemLogMessage(uint MessageId, int[] Args) : Oper { public readonly int[] Args = Args; - protected override void Exec(WorldState ws) => ws.SystemLogMessage.Fire(this); + protected override void Exec(ref WorldState ws) => ws.SystemLogMessage.Fire(this); public override void Write(ReplayRecorder.Output output) { output.EmitFourCC("SLOG"u8).Emit(MessageId); diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs index 79ec545d72..c0efe5ef23 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs @@ -36,9 +36,9 @@ public enum TetherID : uint Freeze = 272 // RorrlohTeh/QorrlohTeh1->Boss } -class FrostingFracasArenaChange(BossModule module) : Components.GenericAOEs(module) +class ArenaChange(BossModule module) : Components.GenericAOEs(module) { - private static readonly AOEShapeDonut donut = new(20, 22.5f); + private static readonly AOEShapeDonut donut = new(20, 23); private AOEInstance? _aoe; public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); @@ -141,7 +141,7 @@ class D021RyoqorTertehStates : StateMachineBuilder public D021RyoqorTertehStates(BossModule module) : base(module) { TrivialPhase() - .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() @@ -150,8 +150,9 @@ public D021RyoqorTertehStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12699)] -public class D021RyoqorTerteh(WorldState ws, Actor primary) : BossModule(ws, primary, new(-108, 119), StartingBounds) +public class D021RyoqorTerteh(WorldState ws, Actor primary) : BossModule(ws, primary, StartingBounds.Center, StartingBounds) { - public static readonly ArenaBounds StartingBounds = new ArenaBoundsCircle(22.5f); - public static readonly ArenaBounds DefaultBounds = new ArenaBoundsCircle(20); + private static readonly WPos arenaCenter = new(-108, 119); + public static readonly ArenaBoundsComplex StartingBounds = new([new Polygon(arenaCenter, 22.5f, 52)]); + public static readonly ArenaBoundsComplex DefaultBounds = new([new Polygon(arenaCenter, 20, 52)]); } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs index 9fbb318715..65dd7ad9df 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D022Kahderyor.cs @@ -42,86 +42,79 @@ public enum AID : uint public enum IconID : uint { - Stackmarker = 62, // Helper - WindShot = 511, // player - EarthenShot = 169, // player - SeedCrystals = 311 // player + WindShot = 511 // player } class WindEarthShot(BossModule module) : Components.GenericAOEs(module) { - private const string risk2Hint = "Walk into a crystal line!"; - private const string stayHint = "Stay inside crystal line!"; + private const string Hint = "Be inside a crystal line!"; private static readonly AOEShapeDonut donut = new(8, 50); private static readonly AOEShapeCircle circle = new(15); - private static readonly Angle am120 = -119.997f.Degrees(); - private static readonly Angle am30 = -29.996f.Degrees(); - private static readonly Angle a80 = 80.001f.Degrees(); - private static readonly Angle am100 = -99.996f.Degrees(); - private static readonly WPos pos1 = new(-43, -57); - private static readonly WPos pos2 = new(-63, -57); - private static readonly WPos pos3 = new(-53, -47); - private static readonly WPos pos4 = new(-53, -67); + private static readonly Angle[] angles = [-119.997f.Degrees(), -29.996f.Degrees(), 80.001f.Degrees(), -99.996f.Degrees()]; + private static readonly WPos[] positions = [new(-43, -57), new(-63, -57), new(-53, -47), new(-53, -67)]; private const int Length = 50; private const uint State = 0x00800040; - private static readonly AOEShapeCustom ENVC21Inverted = CreateShape(pos2, pos3, pos4, am30, am120, a80, 1, true); - private static readonly AOEShapeCustom ENVC21 = CreateShape(pos2, pos3, pos4, am30, am120, a80, 7); - private static readonly AOEShapeCustom ENVC20Inverted = CreateShape(pos1, pos3, pos4, am30, am100, am120, 1, true); - private static readonly AOEShapeCustom ENVC20 = CreateShape(pos1, pos3, pos4, am30, am100, am120, 7); - private AOEInstance? _aoe; + private static readonly AOEShapeCustom ENVC21Inverted = CreateShape(positions[1], positions[2], positions[3], angles[1], angles[0], angles[2], 1, true); + private static readonly AOEShapeCustom ENVC21 = CreateShape(positions[1], positions[2], positions[3], angles[1], angles[0], angles[2], 7); + private static readonly AOEShapeCustom ENVC20Inverted = CreateShape(positions[0], positions[2], positions[3], angles[1], angles[3], angles[0], 1, true); + private static readonly AOEShapeCustom ENVC20 = CreateShape(positions[0], positions[2], positions[3], angles[1], angles[3], angles[0], 7); + public AOEInstance? AOE; + private static readonly WDir am40 = -40.Degrees().ToDirection(), a0 = new(0, 1); - public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(AOE); public override void OnEventEnvControl(byte index, uint state) { - var activation = WorldState.FutureTime(5.9f); - if (state is State or 0x00200010) - { - AOEShape shape = state == State ? donut : circle; - AddAOE(index, state, shape, activation); - } + AddAOE(index, state, state == State ? donut : circle); else if (state is 0x02000001 or 0x04000004 or State or 0x08000004 or 0x01000001) - _aoe = null; + AOE = null; } - private void AddAOE(byte index, uint state, AOEShape shape, DateTime activation) + private void AddAOE(byte index, uint state, AOEShape shape) { - var color = state == State ? Colors.SafeFromAOE : Colors.AOE; - _aoe = index switch + var activation = WorldState.FutureTime(5.9f); + var color = state == State ? Colors.SafeFromAOE : 0; + AOE = index switch { - 0x1E => new(shape, pos1, default, activation), - 0x1F => new(shape, pos2, default, activation), + 0x1E => new(shape, positions[0], default, activation), + 0x1F => new(shape, positions[1], default, activation), 0x20 => new(state == State ? ENVC20Inverted : ENVC20, Arena.Center, default, activation, color), 0x21 => new(state == State ? ENVC21Inverted : ENVC21, Arena.Center, default, activation, color), - _ => _aoe + _ => AOE }; } public override void AddHints(int slot, Actor actor, TextHints hints) { - if (ActiveAOEs(slot, actor).Any(c => !(c.Shape == ENVC20Inverted || c.Shape == ENVC21Inverted))) + if (AOE == null) + return; + var aoeShape = AOE.Value.Shape; + if (aoeShape != ENVC20Inverted && aoeShape != ENVC21Inverted) base.AddHints(slot, actor, hints); - else if (ActiveAOEs(slot, actor).Any(c => (c.Shape == ENVC20Inverted || c.Shape == ENVC21Inverted) && !c.Check(actor.Position))) - hints.Add(risk2Hint); - else if (ActiveAOEs(slot, actor).Any(c => (c.Shape == ENVC20Inverted || c.Shape == ENVC21Inverted) && c.Check(actor.Position))) - hints.Add(stayHint, false); + else if (!AOE.Value.Check(actor.Position)) + hints.Add(Hint); + else + hints.Add(Hint, false); } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { + if (AOE == null) + return; base.AddAIHints(slot, actor, assignment, hints); - var activeAOEs = ActiveAOEs(slot, actor).ToList(); - var containsENVC20 = activeAOEs.Any(c => c.Shape == ENVC20); - var containsENVC21 = activeAOEs.Any(c => c.Shape == ENVC21); + + var aoeShape = AOE.Value.Shape; + var containsENVC20 = aoeShape == ENVC20; + var containsENVC21 = aoeShape == ENVC21; if (containsENVC20 || containsENVC21) { var forbiddenZone = actor.Role != Role.Tank - ? ShapeDistance.Rect(Arena.Center, containsENVC20 ? -40.Degrees() : 0.Degrees(), 20, containsENVC20 ? 1 : 10, 20) + ? ShapeDistance.Rect(Arena.Center, containsENVC20 ? am40 : a0, 20, containsENVC20 ? 1 : 10, 20) : ShapeDistance.InvertedCircle(Arena.Center, 12); - hints.AddForbiddenZone(forbiddenZone, activeAOEs.FirstOrDefault().Activation); + hints.AddForbiddenZone(forbiddenZone, AOE.Value.Activation); } } @@ -132,16 +125,36 @@ private static AOEShapeCustom CreateShape(WPos pos1, WPos pos2, WPos pos3, Angle class WindShotStack(BossModule module) : Components.DonutStack(module, ActionID.MakeSpell(AID.WindShot), (uint)IconID.WindShot, 5, 10, 6, 4, 4) { + private readonly WindEarthShot _aoe = module.FindComponent()!; + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { if (ActiveStacks.Count == 0) return; - var comp = Module.FindComponent()!.ActiveAOEs(slot, actor).ToList(); - var forbidden = new List>(); - foreach (var c in Raid.WithoutSlot(false, true, true).Exclude(actor).Where(x => comp.Any(c => c.Shape is AOEShapeDonut && !c.Check(x.Position) || c.Shape is AOEShapeCustom && c.Check(x.Position)))) - forbidden.Add(ShapeDistance.InvertedCircle(c.Position, Donut.InnerRadius * 0.33f)); - if (forbidden.Count > 0) - hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), ActiveStacks.FirstOrDefault().Activation); + + var comp = _aoe.AOE; + if (comp == null) + return; + + var aoe = comp.Value; + var forbidden = new List>(3); + var party = Raid.WithoutSlot(false, true, true); + + for (var i = 0; i < party.Length; ++i) + { + var p = party[i]; + if (p == actor) + continue; + + var addForbidden = false; + if (aoe.Shape is AOEShapeDonut && !aoe.Check(p.Position) || aoe.Shape is AOEShapeCustom && aoe.Check(p.Position)) + addForbidden = true; + if (addForbidden) + forbidden.Add(ShapeDistance.InvertedCircle(p.Position, 1.66f)); + } + + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), ActiveStacks[0].Activation); } } @@ -159,8 +172,8 @@ class D022KahderyorStates : StateMachineBuilder public D022KahderyorStates(BossModule module) : base(module) { TrivialPhase() - .ActivateOnEnter() .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() @@ -175,8 +188,7 @@ public D022KahderyorStates(BossModule module) : base(module) [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 824, NameID = 12703)] public class D022Kahderyor(WorldState ws, Actor primary) : BossModule(ws, primary, DefaultBounds.Center, DefaultBounds) { - private static readonly WPos arenaCenter = new(-53, -57); - public static readonly ArenaBoundsComplex DefaultBounds = new([new Circle(arenaCenter, 19.5f)], [new Rectangle(new(-72.5f, -57), 0.75f, 20), new Rectangle(new(-53, -37), 20, 1.5f)]); + public static readonly ArenaBoundsComplex DefaultBounds = new([new Polygon(new(-53, -57), 19.5f, 40)], [new Rectangle(new(-72.5f, -57), 0.75f, 20), new Rectangle(new(-53, -37), 20, 1.5f)]); protected override void DrawEnemies(int pcSlot, Actor pc) { @@ -186,12 +198,12 @@ protected override void DrawEnemies(int pcSlot, Actor pc) protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.CrystallineDebris => 2, - OID.Boss => 1, + OID.CrystallineDebris => 1, _ => 0 }; } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs index 2ff83d8277..22e663f234 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D023Gurfurlur.cs @@ -43,7 +43,7 @@ public enum AID : uint Whirlwind = 36311 // Helper->self, no cast, range 5 circle } -class HeavingHaymakerArenaChange(BossModule module) : Components.GenericAOEs(module) +class ArenaChange(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCustom square = new([new Square(D023Gurfurlur.ArenaCenter, 25)], [new Square(D023Gurfurlur.ArenaCenter, 20)]); private AOEInstance? _aoe; @@ -106,91 +106,107 @@ class Whirlwind(BossModule module) : Components.PersistentVoidzone(module, 5, m class GreatFlood(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.GreatFlood), 25, kind: Kind.DirForward) { + private readonly Allfire _aoe = module.FindComponent()!; + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var source = Sources(slot, actor).FirstOrDefault(); - var component = Module.FindComponent()!.ActiveAOEs(slot, actor).Any(); - if (!component && source != default) + if (Casters.Count == 0) + return; + + Source source = default; + var sources = Sources(slot, actor); + foreach (var s in sources) + { + source = s; + break; + } + + var component = _aoe.AOEs; + if (_aoe.AOEs.Count == 0 && source != default) hints.AddForbiddenZone(ShapeDistance.InvertedRect(source.Origin, source.Direction, 15, 0, 20), source.Activation); + } } class Allfire(BossModule module) : Components.GenericAOEs(module) { - private const string Risk2Hint = "Walk into safespot for knockback!", StayHint = "Wait inside safespot for knockback!"; - private bool tutorial; + private const string Hint = "Be inside safespot for knockback!"; private static readonly AOEShapeRect rect = new(10, 5); - private readonly List _aoesWave1 = [], _aoesWave2 = [], _aoesWave3 = []; + public readonly List AOEs = new(16); private static readonly AOEShapeRect safespot = new(15, 10, InvertForbiddenZone: true); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - var source = Module.FindComponent()!.Sources(slot, actor).FirstOrDefault(); - if (source == default) + var count = AOEs.Count; + if (count == 0) + return []; + Components.Knockback.Source source = default; + var sources = Module.FindComponent()!.Sources(slot, actor); + foreach (var s in sources) { - var count1 = _aoesWave1.Count; - var count2 = _aoesWave2.Count; - if (count1 > 0) - foreach (var a in _aoesWave1) - yield return a with { Color = Colors.Danger }; - if (count2 > 0) - foreach (var a in _aoesWave2) - yield return a with { Color = count1 > 0 ? Colors.AOE : Colors.Danger, Risky = count1 == 0 }; - if (count1 == 0 && _aoesWave3.Count > 0) - foreach (var a in _aoesWave3) - yield return a with { Color = count2 > 0 ? Colors.AOE : Colors.Danger, Risky = count2 == 0 }; + source = s; + break; + } + if (source != default) + return [new(safespot, source.Origin, source.Direction, source.Activation, Colors.SafeFromAOE)]; + else + { + var max = count >= 12 ? 12 : count == 8 ? 8 : 4; + var aoes = new AOEInstance[max]; + for (var i = 0; i < max; ++i) + { + var aoe = AOEs[i]; + aoes[i] = (aoe.Activation - AOEs[0].Activation).TotalSeconds < 1 ? aoe with { Color = count > 4 ? Colors.Danger : 0 } : aoe with { Risky = false }; + } + return aoes; } - else if ((_aoesWave3.Count > 0 || _aoesWave1.Count > 0) && source != default) - yield return new(safespot, source.Origin, source.Direction, source.Activation, Colors.SafeFromAOE); - } + public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - var newAOE = new AOEInstance(rect, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell)); - switch ((AID)spell.Action.ID) + if ((AID)spell.Action.ID is AID.Allfire1 or AID.Allfire2 or AID.Allfire3) { - case AID.Allfire1: - _aoesWave1.Add(newAOE); - break; - case AID.Allfire2: - _aoesWave2.Add(newAOE); - break; - case AID.Allfire3: - _aoesWave3.Add(newAOE); - break; + AOEs.Add(new(rect, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); + if (AOEs.Count == 16) + AOEs.SortBy(x => x.Activation); } } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - switch ((AID)spell.Action.ID) - { - case AID.Allfire1: - _aoesWave1.Clear(); - if (!tutorial) - tutorial = true; - break; - case AID.Allfire2: - _aoesWave2.Clear(); - break; - case AID.Allfire3: - _aoesWave3.Clear(); - break; - } + if ((AID)spell.Action.ID is AID.Allfire1 or AID.Allfire2 or AID.Allfire3) + AOEs.RemoveAt(0); } public override void AddHints(int slot, Actor actor, TextHints hints) { - var activeAOEs = ActiveAOEs(slot, actor).ToList(); - if (activeAOEs.Any(c => c.Shape != safespot)) + if (AOEs.Count == 0) + return; + + var activeAOEs = ActiveAOEs(slot, actor); + var hasSafeSpot = false; + var isActorInSafeSpot = true; + + foreach (var aoe in activeAOEs) + { + if (aoe.Shape == safespot) + hasSafeSpot = true; + if (hasSafeSpot) + { + if (!aoe.Check(actor.Position)) + isActorInSafeSpot = false; + break; + } + } + + if (!hasSafeSpot) base.AddHints(slot, actor, hints); else { - var safeSpots = activeAOEs.Where(c => c.Shape == safespot).ToList(); - if (safeSpots.Any(c => !c.Check(actor.Position))) - hints.Add(Risk2Hint); - else if (safeSpots.Any(c => c.Check(actor.Position))) - hints.Add(StayHint, false); + if (!isActorInSafeSpot) + hints.Add(Hint); + else + hints.Add(Hint, false); } } } @@ -206,9 +222,17 @@ class Windswrath1(BossModule module) : Windswrath(module, AID.Windswrath1) { public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var sources = Sources(slot, actor).FirstOrDefault(); - if (sources != default) - hints.AddForbiddenZone(ShapeDistance.InvertedCircle(sources.Origin, 5), sources.Activation); + if (Casters.Count == 0) + return; + Source source = default; + var sources = Sources(slot, actor); + foreach (var s in sources) + { + source = s; + break; + } + if (source != default) + hints.AddForbiddenZone(ShapeDistance.InvertedCircle(source.Origin, 5), source.Activation); } } @@ -217,7 +241,7 @@ class Windswrath2(BossModule module) : Windswrath(module, AID.Windswrath2) private enum Pattern { None, EWEW, WEWE } private Pattern CurrentPattern; private static readonly Angle a15 = 15.Degrees(), a165 = 165.Degrees(), a105 = 105.Degrees(), a75 = 75.Degrees(); - private static readonly WDir offset = new(0, 1); + private readonly Whirlwind _aoe = module.FindComponent()!; public override void OnActorCreated(Actor actor) { @@ -230,29 +254,37 @@ public override void OnActorCreated(Actor actor) } } - public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || !Module.InBounds(pos); + public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => _aoe.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) || !Module.InBounds(pos); public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var sources = Sources(slot, actor).FirstOrDefault(); - var forbidden = new List>(); - var component = Module.FindComponent()?.ActiveAOEs(slot, actor)?.ToList(); - if (component != null && component.Count != 0 && sources != default) + if (Casters.Count == 0) + return; + Source source = default; + var sources = Sources(slot, actor); + foreach (var s in sources) + { + source = s; + break; + } + var forbidden = new List>(4); + Components.GenericAOEs.AOEInstance[] component = [.. _aoe.ActiveAOEs(slot, actor)]; + if (component.Length != 0 && source != default) { base.AddAIHints(slot, actor, assignment, hints); - var timespan = (float)(sources.Activation - WorldState.CurrentTime).TotalSeconds; + var timespan = (float)(source.Activation - WorldState.CurrentTime).TotalSeconds; if (timespan <= 3) { var patternWEWE = CurrentPattern == Pattern.WEWE; - forbidden.Add(ShapeDistance.InvertedCone(sources.Origin - offset, 5, patternWEWE ? a15 : -a15, a15)); - forbidden.Add(ShapeDistance.InvertedCone(sources.Origin + offset, 5, patternWEWE ? -a165 : a165, a15)); - forbidden.Add(ShapeDistance.InvertedCone(sources.Origin, 5, patternWEWE ? a105 : -a105, a15)); - forbidden.Add(ShapeDistance.InvertedCone(sources.Origin, 5, patternWEWE ? -a75 : a75, a15)); + var origin = source.Origin; + forbidden.Add(ShapeDistance.InvertedCone(origin, 5, patternWEWE ? a15 : -a15, a15)); + forbidden.Add(ShapeDistance.InvertedCone(origin, 5, patternWEWE ? -a165 : a165, a15)); + forbidden.Add(ShapeDistance.InvertedCone(origin, 5, patternWEWE ? a105 : -a105, a15)); + forbidden.Add(ShapeDistance.InvertedCone(origin, 5, patternWEWE ? -a75 : a75, a15)); } else - forbidden.Add(ShapeDistance.InvertedCircle(sources.Origin, 8)); - if (forbidden.Count != 0) - hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), sources.Activation); + forbidden.Add(ShapeDistance.InvertedCircle(source.Origin, 8)); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), source.Activation); } } } @@ -262,14 +294,14 @@ class D023GurfurlurStates : StateMachineBuilder public D023GurfurlurStates(BossModule module) : base(module) { TrivialPhase() - .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() diff --git a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs index 9fe23c9f32..4e854ff64e 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs @@ -142,7 +142,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID is AID.DarkII1 or AID.DarkII2) { - _aoes.Add(new(cone, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + _aoes.Add(new(cone, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); if (_aoes.Count == 12) _aoes.SortBy(x => x.Activation); } diff --git a/BossMod/Modules/Endwalker/Trial/T08Asura/T08MyriadAspects.cs b/BossMod/Modules/Endwalker/Trial/T08Asura/T08MyriadAspects.cs index cd145d7d05..b8612a24eb 100644 --- a/BossMod/Modules/Endwalker/Trial/T08Asura/T08MyriadAspects.cs +++ b/BossMod/Modules/Endwalker/Trial/T08Asura/T08MyriadAspects.cs @@ -21,7 +21,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID is AID.MyriadAspects1 or AID.MyriadAspects2) { - _aoes.Add(new(cone, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + _aoes.Add(new(cone, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); if (_aoes.Count == 12) _aoes.SortBy(x => x.Activation); } diff --git a/BossMod/Modules/RealmReborn/Dungeon/D04Halatali/D041Firemane.cs b/BossMod/Modules/RealmReborn/Dungeon/D04Halatali/D041Firemane.cs index 63b1f2fea5..c07c7e1cb7 100644 --- a/BossMod/Modules/RealmReborn/Dungeon/D04Halatali/D041Firemane.cs +++ b/BossMod/Modules/RealmReborn/Dungeon/D04Halatali/D041Firemane.cs @@ -40,7 +40,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID is AID.Fireflow1 or AID.Fireflow2) { - _aoes.Add(new(cone, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + _aoes.Add(new(cone, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); if (_aoes.Count == 8) _aoes.SortBy(x => x.Activation); } diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheHiddenCanalsOfUznair/Airavata.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheHiddenCanalsOfUznair/Airavata.cs index 14c5f45505..29863cc883 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheHiddenCanalsOfUznair/Airavata.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheHiddenCanalsOfUznair/Airavata.cs @@ -51,10 +51,10 @@ public enum AID : uint Hurl = 5352, // Abharamu->location, 3.0s cast, range 6 circle PungentPirouette = 6450, // 1FCF->self, 3.5s cast, range 6+R circle - PluckAndPrune = 6449, // 1FCE->self, 3.5s cast, range 6+R circle + PluckAndPrune = 6449, // CanalEgg->self, 3.5s cast, range 6+R circle HeirloomScream = 6451, // 1FD0->self, 3.5s cast, range 6+R circle - Pollen = 6452, // 1FD1->self, 3.5s cast, range 6+R circle - TearyTwirl = 6448, // 1FCD->self, 3.5s cast, range 6+R circle + Pollen = 6452, // CanalQueen->self, 3.5s cast, range 6+R circle + TearyTwirl = 6448, // CanalOnion->self, 3.5s cast, range 6+R circle Telega = 9630 // Mandragoras/Abharamu/NamazuStickywhisker->self, no cast, single-target, bonus adds disappear } @@ -72,7 +72,7 @@ class Buffet(BossModule module) : Components.SingleTargetCast(module, ActionID.M class RingOfFire(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RingOfFire), 5); class StoneII(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.StoneII)); -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.Abharamu); abstract class Mandragoras(BossModule module, AID aid) : Components.SimpleAOEs(module, ActionID.MakeSpell(aid), 6.84f); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheLostCanalsOfUznair/CanalIcebeast.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheLostCanalsOfUznair/CanalIcebeast.cs index 44d902f847..589130d4fd 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheLostCanalsOfUznair/CanalIcebeast.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheLostCanalsOfUznair/CanalIcebeast.cs @@ -32,7 +32,7 @@ class Eyeshine(BossModule module) : Components.CastGaze(module, ActionID.MakeSpe class AbsoluteZero(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.AbsoluteZero), new AOEShapeCone(45.5f, 45.Degrees())); class Freezeover(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Freezeover), 6); class PlainPound(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.PlainPound), 4.56f); -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.Abharamu); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs index 31fbdfb0ec..c9c9160ff7 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs @@ -95,7 +95,7 @@ public override void DrawArenaBackground(int pcSlot, Actor pc) } } -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs index d04a8c6a93..ca50a56570 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs @@ -39,7 +39,7 @@ class Earthquake1(BossModule module) : Components.SimpleAOEs(module, ActionID.Ma class Earthquake2(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Earthquake2), new AOEShapeDonut(10, 20)); class Earthquake3(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Earthquake3), new AOEShapeDonut(20, 30)); -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs index 8a12ef7d33..204ff82f74 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarBeast.cs @@ -52,7 +52,7 @@ class HeirloomScream(BossModule module) : Mandragoras(module, AID.HeirloomScream class PungentPirouette(BossModule module) : Mandragoras(module, AID.PungentPirouette); class Pollen(BossModule module) : Mandragoras(module, AID.Pollen); -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs index fe052c9eae..5d8c72a629 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs @@ -74,7 +74,7 @@ public override void AddHints(int slot, Actor actor, TextHints hints) } } -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs index 83085f3b87..150aa22852 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs @@ -80,7 +80,7 @@ public override void AddHints(int slot, Actor actor, TextHints hints) } } -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs index c2a3d01212..0a99d8a1fa 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs @@ -47,7 +47,7 @@ class StygianReleaseKB(BossModule module) : Components.KnockbackFromCastTarget(m public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; } -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs index ce15a01298..19165a88d2 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs @@ -78,7 +78,7 @@ class RisingSeasKB(BossModule module) : Components.KnockbackFromCastTarget(modul public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; } -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs index e6999128b0..a0613d212f 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs @@ -33,7 +33,7 @@ class RecklessAbandon(BossModule module) : Components.SingleTargetDelayableCast( class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class AltarSkateneStates : StateMachineBuilder { diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs index c752914daa..384113e9e4 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs @@ -75,7 +75,7 @@ public override void AddHints(int slot, Actor actor, TextHints hints) } } -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs index 6f829db0da..bf34894ce8 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs @@ -57,7 +57,7 @@ class HeirloomScream(BossModule module) : Mandragoras(module, AID.HeirloomScream class PungentPirouette(BossModule module) : Mandragoras(module, AID.PungentPirouette); class Pollen(BossModule module) : Mandragoras(module, AID.Pollen); -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs index 7d6beb6e5b..b440cb06b7 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheWinged.cs @@ -52,7 +52,7 @@ class HeirloomScream(BossModule module) : Mandragoras(module, AID.HeirloomScream class PungentPirouette(BossModule module) : Mandragoras(module, AID.PungentPirouette); class Pollen(BossModule module) : Mandragoras(module, AID.Pollen); -class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 30.Degrees())); +class RaucousScritch(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.RaucousScritch), new AOEShapeCone(8.42f, 60.Degrees())); class Hurl(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Hurl), 6); class Spin(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.AltarMatanga); diff --git a/BossMod/Pathfinding/Map.cs b/BossMod/Pathfinding/Map.cs index ab0609b35b..330c716792 100644 --- a/BossMod/Pathfinding/Map.cs +++ b/BossMod/Pathfinding/Map.cs @@ -230,20 +230,20 @@ public int AddGoal(Func shape, float threshold, int minPriority, in return result; } - public List<(int x, int y, WPos center)> EnumeratePixels() + public (int x, int y, WPos center)[] EnumeratePixels() { - var result = new List<(int x, int y, WPos center)>(Width * Height); + var result = new (int x, int y, WPos center)[(Width * Height)]; var rsq = Resolution * Resolution; // since we then multiply by _localZDivRes, end result is same as * res * rotation.ToDir() var dx = LocalZDivRes.OrthoL() * rsq; var dy = LocalZDivRes * rsq; var cy = Center + (-Width * HalfPixel + HalfPixel) * dx + (-Height * HalfPixel + HalfPixel) * dy; - + var index = 0; for (var y = 0; y < Height; ++y) { var cx = cy; for (var x = 0; x < Width; ++x) { - result.Add((x, y, cx)); + result[index++] = (x, y, cx); cx += dx; } cy += dy; diff --git a/BossMod/Pathfinding/NavigationDecision.cs b/BossMod/Pathfinding/NavigationDecision.cs index 0c9154f67f..6d79c64e13 100644 --- a/BossMod/Pathfinding/NavigationDecision.cs +++ b/BossMod/Pathfinding/NavigationDecision.cs @@ -44,9 +44,9 @@ public enum Decision public Decision DecisionType; private static readonly AI.AIConfig _config = Service.Config.Get(); - public const float DefaultForbiddenZoneCushion = 0.354f; // 0.25 * sqrt(2) = distance from center to a corner for the standard 0.5 map resolution + public const float DefaultForbiddenZoneCushion = 0.70710678f; // multiplier distance from cell center to cell corner - public static NavigationDecision Build(Context ctx, WorldState ws, AIHints hints, Actor player, WPos? targetPos, float targetRadius, Angle targetRot, Positional positional, float playerSpeed = 6, float forbiddenZoneCushion = DefaultForbiddenZoneCushion) + public static NavigationDecision Build(Context ctx, WorldState ws, AIHints hints, Actor player, WPos? targetPos, float targetRadius, Angle targetRot, Positional positional, float playerSpeed = 6) { if (targetRadius < 1) targetRadius = 1; // ensure targetRadius is at least 1 to prevent game from freezing @@ -73,6 +73,7 @@ public static NavigationDecision Build(Context ctx, WorldState ws, AIHints hints } hints.PathfindMapBounds.PathfindMap(ctx.Map, hints.PathfindMapCenter); + var forbiddenZoneCushion = ctx.Map.Resolution * DefaultForbiddenZoneCushion; if (!_config.AllowAIToBeOutsideBounds && IsOutsideBounds(player.Position, ctx)) { @@ -91,7 +92,7 @@ public static NavigationDecision Build(Context ctx, WorldState ws, AIHints hints for (var i = 0; i < len; ++i) { - var inside = localForbiddenZones[i].shapeDistance(player.Position) <= forbiddenZoneCushion - 0.1f; + var inside = localForbiddenZones[i].shapeDistance(player.Position) <= 0.1f; inZone[i] = inside; if (inside) { @@ -327,8 +328,11 @@ public static NavigationDecision FindPathFromOutsideBounds(Context ctx, WPos sta { WPos? closest = null; var closestDistance = float.MaxValue; - foreach (var p in ctx.Map.EnumeratePixels()) + var map = ctx.Map.EnumeratePixels(); + var len = map.Length; + for (var i = 0; i < len; ++i) { + var p = map[i]; if (ctx.Map[p.x, p.y].MaxG > 0) // assume any pixel not marked as blocked is better than being outside of bounds { var distance = (p.center - startPos).LengthSq(); diff --git a/BossMod/Util/ShapeDistance.cs b/BossMod/Util/ShapeDistance.cs index a40af6092f..a98423c5b8 100644 --- a/BossMod/Util/ShapeDistance.cs +++ b/BossMod/Util/ShapeDistance.cs @@ -747,7 +747,7 @@ public static Func InvertedUnion(Func[] funcs) // min // it's an inverted rect of a size equal to one grid cell, with a special adjustment if starting position is in the same cell, but farther than tolerance public static Func PrecisePosition(WPos origin, WDir dir, float cellSize, WPos starting, float tolerance = 0) { - var cellSizeAdj = cellSize + NavigationDecision.DefaultForbiddenZoneCushion; + var cellSizeAdj = cellSize + cellSize * NavigationDecision.DefaultForbiddenZoneCushion; var delta = starting - origin; var dparr = delta.Dot(dir); if (dparr > tolerance && dparr <= cellSizeAdj)