Skip to content

Commit

Permalink
Merge branch 'awgil:master' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
LTS-FFXIV authored Apr 3, 2024
2 parents d7155d8 + 1c5086f commit f8fd511
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 14 deletions.
3 changes: 2 additions & 1 deletion BossMod/Components/Knockback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public abstract class Knockback : CastCounter
{
public enum Kind
{
None,
AwayFromOrigin, // standard knockback - specific distance along ray from origin to target
TowardsOrigin, // standard pull - "knockback" to source - forward along source's direction + 180 degrees
DirForward, // directional knockback - forward along source's direction
Expand Down Expand Up @@ -148,7 +149,7 @@ public override void OnStatusLose(BossModule module, Actor actor, ActorStatus st
Kind.DirForward => s.Direction.ToDirection(),
Kind.DirLeft => s.Direction.ToDirection().OrthoL(),
Kind.DirRight => s.Direction.ToDirection().OrthoR(),
_ => new()
_ => default
};
if (dir == default)
continue; // couldn't determine direction for some reason
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,39 @@

class FatefulWords : Components.Knockback
{
private Dictionary<ulong, float> _playerDistances = new();
private Kind[] _mechanics = new Kind[PartyState.MaxPartySize];

public FatefulWords() : base(ActionID.MakeSpell(AID.FatefulWordsAOE), true) { } // TODO: verify it ignores immunities
public FatefulWords() : base(ActionID.MakeSpell(AID.FatefulWordsAOE), true) { }

public override IEnumerable<Source> Sources(BossModule module, int slot, Actor actor)
{
var dist = _playerDistances.GetValueOrDefault(actor.InstanceID);
if (dist != 0)
yield return new(module.Bounds.Center, dist, module.PrimaryActor.CastInfo?.NPCFinishAt ?? new());
var kind = _mechanics[slot];
if (kind != Kind.None)
yield return new(module.Bounds.Center, 6, module.PrimaryActor.CastInfo?.NPCFinishAt ?? default, Kind: kind);
}

public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status)
{
var dist = (SID)status.ID switch
var kind = (SID)status.ID switch
{
SID.WanderersFate => 6,
SID.SacrificesFate => -6,
_ => 0
SID.WanderersFate => Kind.AwayFromOrigin,
SID.SacrificesFate => Kind.TowardsOrigin,
_ => Kind.None
};
if (dist != 0)
_playerDistances[actor.InstanceID] = dist;
if (kind != Kind.None)
AssignMechanic(module, actor, kind);
}

public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status)
{
if ((SID)status.ID is SID.WanderersFate or SID.SacrificesFate)
_playerDistances.Remove(actor.InstanceID);
AssignMechanic(module, actor, Kind.None);
}

private void AssignMechanic(BossModule module, Actor actor, Kind mechanic)
{
var slot = module.Raid.FindSlot(actor.InstanceID);
if (slot >= 0 && slot < _mechanics.Length)
_mechanics[slot] = mechanic;
}
}
117 changes: 116 additions & 1 deletion BossMod/Replay/Analysis/AbilityInfo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using ImGuiNET;
using BossMod.Components;
using ImGuiNET;
using System.Text;
using static BossMod.ActorCastEvent;

namespace BossMod.ReplayAnalysis;

Expand Down Expand Up @@ -186,6 +188,112 @@ public void Draw()
}
}

class KnockbackAnalysis
{
private record struct Point(Replay Replay, Replay.Action Action, Replay.ActionTarget Target);

private Dictionary<int, List<Point>> _byDistance = new();
private Dictionary<Knockback.Kind, List<Point>> _byKind = new();
private List<Point> _immuneIgnores = new();
private List<Point> _immuneMisses = new();
private List<Point> _transcendentIgnores = new();
private List<Point> _transcendentMisses = new();
private List<Point> _otherMisses = new();

public KnockbackAnalysis(List<(Replay, Replay.Action)> infos)
{
foreach (var (r, a) in infos)
{
foreach (var target in a.Targets)
{
bool hasKnockbacks = false;
foreach (var eff in target.Effects)
{
switch (eff.Type)
{
case ActionEffectType.Knockback:
var kbData = Service.LuminaRow<Lumina.Excel.GeneratedSheets.Knockback>(eff.Value);
var kind = kbData != null ? (KnockbackDirection)kbData.Direction switch
{
KnockbackDirection.AwayFromSource => Knockback.Kind.AwayFromOrigin,
KnockbackDirection.SourceForward => Knockback.Kind.DirForward,
KnockbackDirection.SourceRight => Knockback.Kind.DirRight,
KnockbackDirection.SourceLeft => Knockback.Kind.DirLeft,
_ => Knockback.Kind.None
} : Knockback.Kind.None;
AddPoint(r, a, target, (kbData?.Distance ?? 0) + eff.Param0, kind);
hasKnockbacks = true;
break;
case ActionEffectType.Attract1:
case ActionEffectType.Attract2:
var attrData = Service.LuminaRow<Lumina.Excel.GeneratedSheets.Attract>(eff.Value);
AddPoint(r, a, target, attrData?.MaxDistance ?? 0, Knockback.Kind.TowardsOrigin);
hasKnockbacks = true;
break;
case ActionEffectType.AttractCustom1:
case ActionEffectType.AttractCustom2:
case ActionEffectType.AttractCustom3:
AddPoint(r, a, target, eff.Value, Knockback.Kind.TowardsOrigin);
hasKnockbacks = true;
break;
}
}

if (!hasKnockbacks)
{
if (IsImmune(r, target.Target, a.Timestamp))
_immuneMisses.Add(new(r, a, target));
else if (IsTranscendent(r, target.Target, a.Timestamp))
_transcendentMisses.Add(new(r, a, target));
else
_otherMisses.Add(new(r, a, target));
}
}
}
}

public void Draw(UITree tree)
{
foreach (var (dist, points) in _byDistance)
DrawPoints(tree, $"Distance {dist}", points);
foreach (var (kind, points) in _byKind)
DrawPoints(tree, $"Type {kind}", points);
DrawPoints(tree, "Ignore immunity", _immuneIgnores);
DrawPoints(tree, "Ignore transcendent", _transcendentIgnores);
DrawPoints(tree, "Misses while immune", _immuneMisses);
DrawPoints(tree, "Misses while transcendent", _transcendentMisses);
DrawPoints(tree, "Misses in other states", _otherMisses);
}

private void AddPoint(Replay replay, Replay.Action action, Replay.ActionTarget target, int distance, Knockback.Kind kind)
{
_byDistance.GetOrAdd(distance).Add(new(replay, action, target));
_byKind.GetOrAdd(kind).Add(new(replay, action, target));
if (IsImmune(replay, target.Target, action.Timestamp))
_immuneIgnores.Add(new(replay, action, target));
if (IsTranscendent(replay, target.Target, action.Timestamp))
_transcendentIgnores.Add(new(replay, action, target));
}

private void DrawPoints(UITree tree, string tag, List<Point> points)
{
foreach (var n in tree.Node($"{tag} ({points.Count} instances)", points.Count == 0))
{
foreach (var an in tree.Nodes(points, p => new($"{p.Replay.Path} @ {p.Action.Timestamp:O}: {ReplayUtils.ParticipantPosRotString(p.Action.Source, p.Action.Timestamp)} -> {ReplayUtils.ParticipantString(p.Target.Target, p.Action.Timestamp)}")))
{
tree.LeafNodes(an.Target.Effects, ReplayUtils.ActionEffectString);
}
}
}

private static bool IsImmune(uint sid) => sid is 3054 or (uint)WHM.SID.Surecast or (uint)WAR.SID.ArmsLength or 1722 or (uint)WAR.SID.InnerStrength or 2345; // see Knockback component
private static bool IsImmune(Replay replay, Replay.Participant participant, DateTime timestamp) => replay.Statuses.Any(status => status.Target == participant && status.Time.Contains(timestamp) && IsImmune(status.ID));

// transcendent (after rez) is kind of immune too
private static bool IsTranscendent(uint sid) => sid is 418;
private static bool IsTranscendent(Replay replay, Replay.Participant participant, DateTime timestamp) => replay.Statuses.Any(status => status.Target == participant && status.Time.Contains(timestamp) && IsTranscendent(status.ID));
}

class CasterLinkAnalysis
{
private List<(Replay Replay, Replay.Action Action, float MinDistance)> _points = new();
Expand Down Expand Up @@ -235,6 +343,7 @@ class ActionData
public DamageFalloffAnalysis? DamageFalloffAnalysisDistFromSource;
public DamageFalloffAnalysis? DamageFalloffAnalysisMinCoord;
public GazeAnalysis? GazeAnalysis;
public KnockbackAnalysis? KnockbackAnalysis;
public CasterLinkAnalysis? CasterLinkAnalysis;
}

Expand Down Expand Up @@ -369,6 +478,12 @@ public void Draw(UITree tree)
data.GazeAnalysis = new(data.Instances);
data.GazeAnalysis.Draw();
}
foreach (var an in tree.Node("Knockback analysis"))
{
if (data.KnockbackAnalysis == null)
data.KnockbackAnalysis = new(data.Instances);
data.KnockbackAnalysis.Draw(tree);
}
foreach (var an in tree.Node("Caster link analysis"))
{
if (data.CasterLinkAnalysis == null)
Expand Down

0 comments on commit f8fd511

Please sign in to comment.