Skip to content

Commit

Permalink
Merge branch 'awgil:master' into rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
Akechi-kun authored Feb 8, 2025
2 parents 01da025 + ce8556c commit 80d9a12
Show file tree
Hide file tree
Showing 19 changed files with 172 additions and 11 deletions.
14 changes: 14 additions & 0 deletions BossMod/ActionQueue/ActionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,20 @@ public void Dispose()
public static Actor? FindEsunaTarget(WorldState ws) => ws.Party.WithoutSlot().FirstOrDefault(p => p.Statuses.Any(s => Utils.StatusIsRemovable(s.ID)));
public static Actor? SmartTargetEsunable(WorldState ws, Actor player, Actor? primaryTarget, AIHints hints) => SmartTargetFriendly(primaryTarget) ?? FindEsunaTarget(ws) ?? player;

// check if dashing to target will put the player inside a forbidden zone
// TODO should we check if dash trajectory will cross any zones with imminent activation?
public static bool PreventDashIfDangerous(WorldState _, Actor player, Actor? target, AIHints hints)
{
if (target == null || !Service.Config.Get<ActionTweaksConfig>().PreventDangerousDash)
return false;

var dist = player.DistanceToHitbox(target);
var dir = player.DirectionTo(target).Normalized();
var src = player.Position;
var proj = dist > 0 ? src + dir * MathF.Max(0, dist) : src;
return hints.ForbiddenZones.Any(d => d.shapeDistance(proj) < 0);
}

public BitMask SpellAllowedClasses(Lumina.Excel.Sheets.Action data)
{
BitMask res = default;
Expand Down
2 changes: 2 additions & 0 deletions BossMod/ActionQueue/Casters/RDM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,7 @@ private void Customize(ActionDefinitions d)
{
d.RegisterChargeIncreaseTrait(AID.Acceleration, TraitID.EnhancedAcceleration);
// *** add any properties that can't be autogenerated here ***

d.Spell(AID.CorpsACorps)!.ForbidExecute = ActionDefinitions.PreventDashIfDangerous;
}
}
2 changes: 2 additions & 0 deletions BossMod/ActionQueue/Casters/SMN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,7 @@ private void Customize(ActionDefinitions d)
{
d.RegisterChargeIncreaseTrait(AID.RadiantAegis, TraitID.EnhancedRadiantAegis);
// *** add any properties that can't be autogenerated here ***

d.Spell(AID.CrimsonCyclone)!.ForbidExecute = ActionDefinitions.PreventDashIfDangerous;
}
}
2 changes: 2 additions & 0 deletions BossMod/ActionQueue/Melee/DRG.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ private void Customize(ActionDefinitions d)
//d.Spell(AID.TrueThrust)!.TransformAction = d.Spell(AID.RaidenThrust)!.TransformAction = () => ActionID.MakeSpell(_state.BestTrueThrust);
//d.Spell(AID.DoomSpike)!.TransformAction = d.Spell(AID.DraconianFury)!.TransformAction = () => ActionID.MakeSpell(_state.BestDoomSpike);
//d.Spell(AID.Geirskogul)!.TransformAction = d.Spell(AID.Nastrond)!.TransformAction = () => ActionID.MakeSpell(_state.BestGeirskogul);

d.Spell(AID.Stardiver)!.ForbidExecute = d.Spell(AID.DragonfireDive)!.ForbidExecute = ActionDefinitions.PreventDashIfDangerous;
}

public float EffectApplicationDelay(AID aid) => aid switch
Expand Down
2 changes: 2 additions & 0 deletions BossMod/ActionQueue/Melee/NIN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ public void Dispose() { }
private void Customize(ActionDefinitions d)
{
d.RegisterChargeIncreaseTrait(AID.Shukuchi, TraitID.EnhancedShukuchiII);

d.Spell(AID.ForkedRaiju)!.ForbidExecute = ActionDefinitions.PreventDashIfDangerous;
}
}

2 changes: 2 additions & 0 deletions BossMod/ActionQueue/Melee/SAM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,7 @@ private void Customize(ActionDefinitions d)
// upgrades (TODO: don't think we actually care...)
//d.Spell(AID.Iaijutsu)!.TransformAction = () => ActionID.MakeSpell(_state.BestIai);
//d.Spell(AID.MeikyoShisui)!.Condition = _ => _state.MeikyoLeft == 0;

d.Spell(AID.HissatsuGyoten)!.ForbidExecute = ActionDefinitions.PreventDashIfDangerous;
}
}
10 changes: 10 additions & 0 deletions BossMod/ActionQueue/Tanks/PLD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ public void Dispose() { }

private void Customize(ActionDefinitions d)
{
d.Spell(AID.PassageOfArms)!.TransformAngle = (ws, player, _, _) => _config.Wings switch
{
PLDConfig.WingsBehavior.CharacterForward => player.Rotation + 180.Degrees(),
PLDConfig.WingsBehavior.CameraBackward => ws.Client.CameraAzimuth + 180.Degrees(),
PLDConfig.WingsBehavior.CameraForward => ws.Client.CameraAzimuth,
_ => null
};

d.Spell(AID.Intervention)!.SmartTarget = ActionDefinitions.SmartTargetCoTank;
d.Spell(AID.HolySpirit)!.ForbidExecute = (ws, player, _, _) => _config.ForbidEarlyHolySpirit && !player.InCombat && ws.Client.CountdownRemaining > 1.75f;
d.Spell(AID.ShieldLob)!.ForbidExecute = (ws, player, _, _) => _config.ForbidEarlyShieldLob && !player.InCombat && ws.Client.CountdownRemaining > 0.7f;
Expand All @@ -162,5 +170,7 @@ private void Customize(ActionDefinitions d)
//d.Spell(AID.HallowedGround)!.EffectDuration = 10;
//d.Spell(AID.DivineVeil)!.EffectDuration = 30;
// TODO: Intervention effect duration?

d.Spell(AID.Intervene)!.ForbidExecute = ActionDefinitions.PreventDashIfDangerous;
}
}
2 changes: 2 additions & 0 deletions BossMod/ActionQueue/Tanks/WAR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,7 @@ private void Customize(ActionDefinitions d)
//d.Spell(AID.StormEye)!.TransformAction = config.STCombos ? () => ActionID.MakeSpell(Rotation.GetNextSTComboAction(ComboLastMove, AID.StormEye)) : null;
//d.Spell(AID.StormPath)!.TransformAction = config.STCombos ? () => ActionID.MakeSpell(Rotation.GetNextSTComboAction(ComboLastMove, AID.StormPath)) : null;
//d.Spell(AID.MythrilTempest)!.TransformAction = config.AOECombos ? () => ActionID.MakeSpell(Rotation.GetNextAOEComboAction(ComboLastMove)) : null;

d.Spell(AID.Onslaught)!.ForbidExecute = d.Spell(AID.PrimalRend)!.ForbidExecute = ActionDefinitions.PreventDashIfDangerous;
}
}
3 changes: 3 additions & 0 deletions BossMod/ActionTweaks/ActionTweaksConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,7 @@ public enum GroundTargetingMode
}
[PropertyDisplay("Automatic target selection for ground-targeted abilities")]
public GroundTargetingMode GTMode = GroundTargetingMode.Manual;

[PropertyDisplay("Try to prevent dashing into AOEs", tooltip: "Prevent automatic use of damaging gap closers (like WAR Onslaught) if they would move you into a dangerous area. May not work as expected in instances that do not have modules.")]
public bool PreventDangerousDash = false;
}
18 changes: 18 additions & 0 deletions BossMod/ActionTweaks/ClassActions/PLDConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,22 @@ class PLDConfig : ConfigNode

[PropertyDisplay("Prevent use of 'Shield Lob' too early when in pre-pull (if Holy Spirit is not unlocked)")]
public bool ForbidEarlyShieldLob = true;

public enum WingsBehavior : uint
{
[PropertyDisplay("Game default (character-relative, backwards)")]
Default = 0,

[PropertyDisplay("Character-relative, forwards")]
CharacterForward = 1,

[PropertyDisplay("Camera-relative, backwards")]
CameraBackward = 2,

[PropertyDisplay("Camera-relative, forwards")]
CameraForward = 3,
}

[PropertyDisplay("Passage of Arms direction")]
public WingsBehavior Wings = WingsBehavior.Default;
}
2 changes: 1 addition & 1 deletion BossMod/ActionTweaks/SmartRotationTweak.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public sealed class SmartRotationTweak(WorldState ws, AIHints hints)
public Angle? GetSpellOrientation(uint spellId, WPos playerPos, bool targetIsSelf, WPos? targetPos, WPos targetLoc)
{
var data = Service.LuminaRow<Lumina.Excel.Sheets.Action>(spellId);
if (data == null || !data.Value.NeedToFaceTarget) // does not require facing
if (data == null || !data.Value.NeedToFaceTarget || data.Value.Range == 0) // does not require facing
return null;
if (data.Value.TargetArea)
return Angle.FromDirection(targetLoc - playerPos);
Expand Down
56 changes: 56 additions & 0 deletions BossMod/Autorotation/MiscAI/AutoPull.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using BossMod.Autorotation.xan;

namespace BossMod.Autorotation.MiscAI;

public sealed class AutoPull(RotationModuleManager manager, Actor player) : RotationModule(manager, player)
{
public enum Track { QuestBattle, DeepDungeon, EpicEcho, Hunt }

public static RotationModuleDefinition Definition()
{
var def = new RotationModuleDefinition("Misc AI: Auto-pull", "Automatically attack passive mobs in certain circumstances", "Misc", "xan", RotationModuleQuality.Basic, new(~0ul), 1000, Order: RotationModuleOrder.HighLevel, CanUseWhileRoleplaying: true);

def.AbilityTrack(Track.QuestBattle, "Automatically attack solo duty bosses");
def.AbilityTrack(Track.DeepDungeon, "Automatically attack deep dungeon bosses when solo");
def.AbilityTrack(Track.EpicEcho, "Automatically attack all targets if the Epic Echo status is present (i.e. when unsynced)");
def.AbilityTrack(Track.Hunt, "Automatically attack hunt marks once they have already been pulled");

return def;
}

public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving)
{
if (Player.InCombat || primaryTarget != null || World.Client.CountdownRemaining != null)
return;

var enabled = false;

if (strategy.Enabled(Track.QuestBattle))
enabled |= Bossmods.ActiveModule?.Info?.Category == BossModuleInfo.Category.Quest;

if (strategy.Enabled(Track.DeepDungeon))
enabled |= Bossmods.ActiveModule?.Info?.Category == BossModuleInfo.Category.DeepDungeon && World.Party.WithoutSlot().Count() == 1;

if (strategy.Enabled(Track.EpicEcho))
enabled |= Player.Statuses.Any(s => s.ID == 2734);

// TODO set HP threshold lower, or remove entirely? want to avoid getting one guy'd by an early puller
if (strategy.Enabled(Track.Hunt) && Bossmods.ActiveModule?.Info?.Category == BossModuleInfo.Category.Hunt && Bossmods.ActiveModule?.PrimaryActor is Actor p && p.InCombat && p.HPRatio < 0.95f)
{
Hints.SetPriority(p, 0);
primaryTarget = p;
return;
}

if (enabled)
{
var bestEnemy = Hints.PotentialTargets.Where(t => t.Priority == AIHints.Enemy.PriorityUndesirable).MinBy(p => Player.DistanceToHitbox(p.Actor));
if (bestEnemy != null)
{
bestEnemy.Priority = 0;
primaryTarget = bestEnemy.Actor;
}
}
}
}

7 changes: 6 additions & 1 deletion BossMod/Autorotation/MiscAI/NormalMovement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,13 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget,
CastStrategy.Greedy => float.MaxValue,
_ => 0,
};
Hints.MaxCastTime = Math.Min(Hints.MaxCastTime, maxCastTime);
Hints.MaxCastTime = Math.Max(0, Math.Min(Hints.MaxCastTime, maxCastTime));
Hints.ForceCancelCast |= castStrategy == CastStrategy.DropMove;
if (castStrategy is CastStrategy.Leeway && Player.CastInfo is { } castInfo)
{
var effectiveCastRemaining = Math.Max(0, castInfo.RemainingTime - 0.5f);
Hints.ForceCancelCast |= Hints.MaxCastTime < effectiveCastRemaining;
}
}

private float CalculateUnobstructedPathLength(Angle dir)
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Autorotation/Standard/xan/AI/Healer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private void RunForTank(Action<Actor, PartyMemberState> tankFun)
tankFun(World.Party[tankSlot]!, Health.PartyMemberStates[tankSlot]!);
}

private IEnumerable<Actor> LightParty => World.Party.WithoutSlot(excludeAlliance: true, excludeNPCs: Health.HaveRealPartyMembers);
private IEnumerable<Actor> LightParty => Health.TrackedMembers.Select(x => x.Item2);

public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving)
{
Expand Down
16 changes: 11 additions & 5 deletions BossMod/Autorotation/Standard/xan/AI/TrackPartyHealth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ public record PartyHealthState
public readonly PartyMemberState[] PartyMemberStates = new PartyMemberState[PartyState.MaxAllies];
public PartyHealthState PartyHealth { get; private set; } = new();

public bool HaveRealPartyMembers { get; private set; }
private bool _haveRealPartyMembers;
private BitMask _trackedActors;

public IEnumerable<(int, Actor)> TrackedMembers => World.Party.WithSlot().IncludedInMask(_trackedActors);

// looking up this field in sheets is noticeably expensive somehow
private static readonly Dictionary<uint, bool> _esunaCache = [];
Expand Down Expand Up @@ -118,15 +121,17 @@ public void Update(AIHints Hints)
foreach (var caster in World.Party.WithoutSlot(excludeAlliance: true).Where(a => a.CastInfo?.IsSpell(BossMod.WHM.AID.Esuna) ?? false))
esunas.Set(World.Party.FindSlot(caster.CastInfo!.TargetID));

HaveRealPartyMembers = false;
_haveRealPartyMembers = false;

_trackedActors.Reset();

for (var i = 0; i < PartyState.MaxAllies; i++)
{
var shouldSkip = false;
if (i >= PartyState.MaxPartySize)
{
// if we are running content with normal party, either duty support or human players, NPC allies should be ignored entirely
if (HaveRealPartyMembers)
if (_haveRealPartyMembers)
shouldSkip = true;

// otherwise alliance should be skipped since healing actions generally can't target them
Expand All @@ -137,13 +142,14 @@ public void Update(AIHints Hints)
var actor = World.Party[i];
ref var state = ref PartyMemberStates[i];
state.Slot = i;
if (actor == null || actor.IsDead || actor.HPMP.MaxHP == 0 || shouldSkip)
if (actor == null || actor.IsDead || actor.HPMP.MaxHP == 0 || actor.FateID > 0 || shouldSkip)
{
state.PredictedHP = state.PredictedHPMissing = 0;
state.PredictedHPRatio = state.PendingHPRatio = 1;
}
else
{
_trackedActors[i] = true;
state.PredictedHP = actor.PredictedHPRaw;
state.PredictedHPMissing = (int)actor.HPMP.MaxHP - state.PredictedHP;
state.PredictedHPRatio = state.PendingHPRatio = (float)state.PredictedHP / actor.HPMP.MaxHP;
Expand Down Expand Up @@ -182,7 +188,7 @@ public void Update(AIHints Hints)
foreach (var enemy in Hints.PotentialTargets)
{
var targetSlot = World.Party.FindSlot(enemy.Actor.TargetID);
if (targetSlot >= 0)
if (_trackedActors[targetSlot])
{
ref var state = ref PartyMemberStates[targetSlot];
state.AttackerStrength += enemy.AttackStrength;
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Autorotation/UIRotationWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public override void Draw()
ImGui.TextUnformatted($"GCD={_mgr.WorldState.Client.Cooldowns[ActionDefinitions.GCDGroup].Remaining:f3}, AnimLock={_amex.EffectiveAnimationLock:f3}+{_amex.AnimationLockDelayEstimate:f3}, Combo={_amex.ComboTimeLeft:f3}, RBIn={_mgr.Bossmods.RaidCooldowns.NextDamageBuffIn():f3}");
foreach (var a in _mgr.Hints.ActionsToExecute.Entries)
{
ImGui.TextUnformatted($"> {a.Action} ({a.Priority:f2})");
ImGui.TextUnformatted($"> {a.Action} ({a.Priority:f2}) @ ({a.Target?.Name ?? "<none>"})");
}
}

Expand Down
34 changes: 34 additions & 0 deletions BossMod/Debug/MainDebugWindow.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BossMod.Autorotation;
using BossMod.Autorotation.xan.AI;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
Expand Down Expand Up @@ -88,6 +89,10 @@ public override unsafe void Draw()
{
_debugAutorot.Draw();
}
if (ImGui.CollapsingHeader("Party health"))
{
DrawPartyHealth();
}
if (ImGui.CollapsingHeader("Solo duty module"))
{
if (zmm.ActiveModule is QuestBattle.QuestBattle qb)
Expand Down Expand Up @@ -240,6 +245,35 @@ private void DrawCastingEnemiesList()
ImGui.EndTable();
}

private readonly TrackPartyHealth _partyHealth = new(ws);

private void DrawPartyHealth()
{
_partyHealth.Update(autorot.Hints);

var overall = _partyHealth.PartyHealth;

ImGui.TextUnformatted($"Avg: {overall.Avg * 100:f1}");
ImGui.TextUnformatted($"StD: {overall.StdDev:f3}");

ImGui.BeginTable("partyhealth", 4, ImGuiTableFlags.Resizable);
ImGui.TableSetupColumn("Name");
ImGui.TableSetupColumn("HP");
ImGui.TableSetupColumn("Type");
ImGui.TableHeadersRow();
foreach (var (_, actor) in _partyHealth.TrackedMembers)
{
ImGui.TableNextColumn();
ImGui.TextUnformatted(actor.Name);
ImGui.TableNextColumn();
ImGui.TextUnformatted($"{actor.HPMP.CurHP} ({actor.PendingHPDiffence}) / {actor.HPMP.MaxHP} ({actor.HPRatio * 100:f1}% / {actor.PredictedHPRatio * 100:f1}%)");
ImGui.TableNextColumn();
ImGui.TextUnformatted($"{actor.Type}");
ImGui.TableNextRow();
}
ImGui.EndTable();
}

private unsafe void DrawTargets()
{
var cursorPos = amex.GetWorldPosUnderCursor();
Expand Down
3 changes: 2 additions & 1 deletion BossMod/Framework/MovementOverride.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ private void RMIWalkDetour(void* self, float* sumLeft, float* sumForward, float*
// movement override logic
// note: currently we follow desired direction, only if user does not have any input _or_ if manual movement is blocked
// this allows AI mode to move even if movement is blocked (TODO: is this the right behavior? AI mode should try to avoid moving while casting anyway...)
if ((movementAllowed || misdirectionMode) && ActualMove == default && DirectionToDestination(false) is var relDir && relDir != null)
var allowAuto = movementAllowed ? !MovementBlocked : misdirectionMode;
if (allowAuto && ActualMove == default && DirectionToDestination(false) is var relDir && relDir != null)
{
ActualMove = relDir.Value.h.ToDirection();
}
Expand Down
4 changes: 3 additions & 1 deletion BossMod/Modules/RealmReborn/Trial/T01IfritN/T01IfritN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public override void AddGlobalHints(GlobalHints hints)
class Incinerate(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.Incinerate), new AOEShapeCone(16, 60.Degrees())); // TODO: verify angle
class Eruption(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.EruptionAOE), 8);
class RadiantPlume(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.RadiantPlumeAOE), 8);
class Nails(BossModule module) : Components.Adds(module, (uint)OID.InfernalNail, 2);

class T01IfritNStates : StateMachineBuilder
{
Expand All @@ -49,7 +50,8 @@ public T01IfritNStates(BossModule module) : base(module)
.ActivateOnEnter<Hints>()
.ActivateOnEnter<Incinerate>()
.ActivateOnEnter<Eruption>()
.ActivateOnEnter<RadiantPlume>();
.ActivateOnEnter<RadiantPlume>()
.ActivateOnEnter<Nails>();
}
}

Expand Down

0 comments on commit 80d9a12

Please sign in to comment.