Skip to content

Commit

Permalink
Adding pending dispels and special case for striking dummies.
Browse files Browse the repository at this point in the history
  • Loading branch information
awgil committed Jan 18, 2025
1 parent 9668692 commit e9400d8
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 9 deletions.
2 changes: 1 addition & 1 deletion BossMod/Autorotation/RotationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public int FindDutyActionSlot(ActionID action, ActionID other)

protected (float Left, float In) EstimateRaidBuffTimings(Actor? primaryTarget)
{
if (primaryTarget?.OID != 0x385)
if (primaryTarget == null || !primaryTarget.IsStrikingDummy)
return (Bossmods.RaidCooldowns.DamageBuffLeft(Player), Bossmods.RaidCooldowns.NextDamageBuffIn());

// hack for a dummy: expect that raidbuffs appear at 7.8s and then every 120s
Expand Down
9 changes: 6 additions & 3 deletions BossMod/Data/Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public record struct ActorModelState(byte ModelState, byte AnimState1, byte Anim

public record struct PendingEffect(uint GlobalSequence, int TargetIndex, ulong SourceInstanceId, DateTime Expiration);
public record struct PendingEffectDelta(PendingEffect Effect, int Value);
public record struct PendingEffectStatus(PendingEffect Effect, uint StatusId, byte ExtraLo);
public record struct PendingEffectStatus(PendingEffect Effect, uint StatusId);
public record struct PendingEffectStatusExtra(PendingEffect Effect, uint StatusId, byte ExtraLo);

public sealed class Actor(ulong instanceID, uint oid, int spawnIndex, string name, uint nameID, ActorType type, Class classID, int level, Vector4 posRot, float hitboxRadius = 1, ActorHPMP hpmp = default, bool targetable = true, bool ally = false, ulong ownerID = 0, uint fateID = 0)
{
Expand Down Expand Up @@ -108,7 +109,8 @@ public sealed class Actor(ulong instanceID, uint oid, int spawnIndex, string nam
// all pending lists are sorted by expiration time
public List<PendingEffectDelta> PendingHPDifferences = []; // damage and heal effects applied to the target that were not confirmed yet
public List<PendingEffectDelta> PendingMPDifferences = [];
public List<PendingEffectStatus> PendingStatuses = [];
public List<PendingEffectStatusExtra> PendingStatuses = [];
public List<PendingEffectStatus> PendingDispels = [];
public List<PendingEffect> PendingKnockbacks = [];

public Role Role => Class.GetRole();
Expand All @@ -120,13 +122,14 @@ public sealed class Actor(ulong instanceID, uint oid, int spawnIndex, string nam
public bool Omnidirectional => Utils.CharacterIsOmnidirectional(OID);
public bool IsDeadOrDestroyed => IsDead || IsDestroyed;
public bool IsFriendlyNPC => Type == ActorType.Enemy && IsAlly && IsTargetable;
public bool IsStrikingDummy => NameID == 541; // this is a hack, but striking dummies are special in some ways
public int CharacterSpawnIndex => SpawnIndex < 200 && (SpawnIndex & 1) == 0 ? (SpawnIndex >> 1) : -1; // [0,100) for 'real' characters, -1 otherwise
public int PendingHPDiffence => PendingHPDifferences.Sum(p => p.Value);
public int PendingMPDiffence => PendingMPDifferences.Sum(p => p.Value);
public int PredictedHPRaw => (int)HPMP.CurHP + PendingHPDiffence;
public int PredictedMPRaw => (int)HPMP.CurMP + PendingMPDiffence;
public int PredictedHPClamped => Math.Clamp(PredictedHPRaw, 0, (int)HPMP.MaxHP);
public bool PredictedDead => PredictedHPRaw <= 1;
public bool PredictedDead => PredictedHPRaw <= 1 && !IsStrikingDummy;

// if expirationForPredicted is not null, search pending first, and return one if found; in that case only low byte of extra will be set
public ActorStatus? FindStatus(uint sid, DateTime? expirationForPending = null)
Expand Down
6 changes: 6 additions & 0 deletions BossMod/Data/ActorState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ private void AddPendingEffects(Actor source, ActorCastEvent ev, DateTime timesta
// note: effect reapplication (eg kardia) or some 'instant' effects (eg ast draw/earthly star) won't get confirmations
effTarget.PendingStatuses.Add(new(header, eff.Value, eff.Param2));
break;
case ActionEffectType.RecoveredFromStatusEffect:
case ActionEffectType.LoseStatusEffectTarget:
case ActionEffectType.LoseStatusEffectSource:
effTarget.PendingDispels.Add(new(header, eff.Value));
break;
case ActionEffectType.Knockback:
case ActionEffectType.Attract1:
case ActionEffectType.Attract2:
Expand All @@ -114,6 +119,7 @@ private void RemovePendingEffects(Actor target, RemovePendingEffectPredicate pre
target.PendingHPDifferences.RemoveAll(e => predicate(e.Effect));
target.PendingMPDifferences.RemoveAll(e => predicate(e.Effect));
target.PendingStatuses.RemoveAll(e => predicate(e.Effect));
target.PendingDispels.RemoveAll(e => predicate(e.Effect));
target.PendingKnockbacks.RemoveAll(e => predicate(e));
}

Expand Down
14 changes: 9 additions & 5 deletions BossMod/Replay/Visualization/ReplayDetailsWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,24 +301,28 @@ private void DrawCommonColumns(Actor actor)
var numRealStatuses = actor.Statuses.Count(s => s.ID != 0);
var mouseOffset = ImGui.GetMousePos() - ImGui.GetWindowPos() - ImGui.GetCursorPos();
var mouseInColumn = mouseOffset.X >= 0 && mouseOffset.Y >= 0 && mouseOffset.X < ImGui.GetColumnWidth() && mouseOffset.Y < ImGui.GetFontSize() + 2 * ImGui.GetStyle().FramePadding.Y;
ImGui.TextUnformatted($"{(actor.PendingKnockbacks.Count > 0 ? "Knockbacks pending, " : "")}{(actor.MountId != 0 ? $"Mounted ({actor.MountId}), " : "")}{numRealStatuses} + {actor.PendingStatuses.Count} statuses");
if (mouseInColumn && numRealStatuses + actor.PendingStatuses.Count > 0)
ImGui.TextUnformatted($"{(actor.PendingKnockbacks.Count > 0 ? "Knockbacks pending, " : "")}{(actor.MountId != 0 ? $"Mounted ({actor.MountId}), " : "")}{numRealStatuses} + {actor.PendingStatuses.Count} statuses, {actor.PendingDispels.Count} dispels");
if (mouseInColumn && numRealStatuses + actor.PendingStatuses.Count + actor.PendingDispels.Count > 0)
{
using var tooltip = ImRaii.Tooltip();
if (tooltip)
{
string fromString(ulong instanceId) => instanceId == 0 ? "" : $", from {_player.WorldState.Actors.Find(instanceId)?.ToString() ?? instanceId.ToString("X")}";
string fromString(string prefix, ulong instanceId) => instanceId == 0 ? "" : $", {prefix} {_player.WorldState.Actors.Find(instanceId)?.ToString() ?? instanceId.ToString("X")}";
for (int i = 0; i < actor.Statuses.Length; ++i)
{
ref var s = ref actor.Statuses[i];
if (s.ID != 0)
{
ImGui.TextUnformatted($"[{i}] {Utils.StatusString(s.ID)} ({s.Extra}): {Utils.StatusTimeString(s.ExpireAt, _player.WorldState.CurrentTime)}{fromString(s.SourceID)}");
ImGui.TextUnformatted($"[{i}] {Utils.StatusString(s.ID)} ({s.Extra}): {Utils.StatusTimeString(s.ExpireAt, _player.WorldState.CurrentTime)}{fromString("from", s.SourceID)}");
}
}
foreach (ref var s in actor.PendingStatuses.AsSpan())
{
ImGui.TextUnformatted($"[pending] {Utils.StatusString(s.StatusId)} ({s.ExtraLo}){fromString(s.Effect.SourceInstanceId)}");
ImGui.TextUnformatted($"[pending] {Utils.StatusString(s.StatusId)} ({s.ExtraLo}){fromString("from", s.Effect.SourceInstanceId)}");
}
foreach (ref var s in actor.PendingDispels.AsSpan())
{
ImGui.TextUnformatted($"[dispel] {Utils.StatusString(s.StatusId)}{fromString("by", s.Effect.SourceInstanceId)}");
}
}
}
Expand Down

0 comments on commit e9400d8

Please sign in to comment.