Skip to content

Commit

Permalink
Merge pull request #603 from FFXIV-CombatReborn/mergeWIP
Browse files Browse the repository at this point in the history
Merge wip
  • Loading branch information
CarnifexOptimus authored Feb 13, 2025
2 parents da48fd3 + b7bd753 commit 92fb71b
Show file tree
Hide file tree
Showing 405 changed files with 12,612 additions and 5,106 deletions.
40 changes: 36 additions & 4 deletions BossMod/ActionQueue/ActionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,14 @@ public int ActualMainCooldownGroup(ReadOnlySpan<ClientState.DutyAction> dutyActi
: MainCooldownGroup;

// for multi-charge abilities, action is ready when elapsed >= single-charge cd; assume that if any multi-charge actions share cooldown group, they have same cooldown - otherwise dunno how it should work
// TODO: use adjusted cooldown
// note: GCDs with multiple charges can be affected OR unaffected by haste depending on how many charges the user currently has access to
// the only separate-cooldown GCD that increases to >1 charge via trait is currently MCH Drill; others have multiple charges at unlock - SGE Phlegma, RPR Soul Slice, BLU Surpanakha
public float MainReadyIn(ReadOnlySpan<Cooldown> cooldowns, ReadOnlySpan<ClientState.DutyAction> dutyActions)
{
if (MainCooldownGroup < 0)
return 0;
var cdg = cooldowns[ActualMainCooldownGroup(dutyActions)];
return !IsMultiCharge || cdg.Total < Cooldown ? cdg.Remaining : Cooldown - cdg.Elapsed;
return cdg.Total > 0 ? Math.Max(0, cdg.Total / MaxChargesAtCap() - cdg.Elapsed) : 0;
}

public float ExtraReadyIn(ReadOnlySpan<Cooldown> cooldowns) => ExtraCooldownGroup >= 0 ? cooldowns[ExtraCooldownGroup].Remaining : 0;
Expand All @@ -132,7 +133,7 @@ public float ChargeCapIn(ReadOnlySpan<Cooldown> cooldowns, ReadOnlySpan<ClientSt
if (MainCooldownGroup < 0)
return 0;
var cdg = cooldowns[ActualMainCooldownGroup(dutyActions)];
return cdg.Total > 0 ? (MaxChargesAtLevel(level) * Cooldown - cdg.Elapsed) : 0;
return cdg.Total > 0 ? Math.Max(0, MaxChargesAtLevel(level) * cdg.Total / MaxChargesAtCap() - cdg.Elapsed) : 0;
}

public bool IsUnlocked(WorldState ws, Actor player)
Expand Down Expand Up @@ -173,6 +174,14 @@ public sealed class ActionDefinitions : IDisposable
public static readonly ActionID IDPotionInt = new(ActionType.Item, 1044165); // hq grade 2 gemdraught of intelligence
public static readonly ActionID IDPotionMnd = new(ActionType.Item, 1044166); // hq grade 2 gemdraught of mind

// deep dungeon consumables
public static readonly ActionID IDPotionSustaining = new(ActionType.Item, 20309);
public static readonly ActionID IDPotionMax = new(ActionType.Item, 1013637);
public static readonly ActionID IDPotionEmpyrean = new(ActionType.Item, 23163);
public static readonly ActionID IDPotionSuper = new(ActionType.Item, 1023167);
public static readonly ActionID IDPotionOrthos = new(ActionType.Item, 38944);
public static readonly ActionID IDPotionHyper = new(ActionType.Item, 1038956);

// special general actions that we support
public static readonly ActionID IDGeneralLimitBreak = new(ActionType.General, 3);
public static readonly ActionID IDGeneralSprint = new(ActionType.General, 4);
Expand Down Expand Up @@ -219,6 +228,13 @@ private ActionDefinitions()
RegisterPotion(IDPotionInt);
RegisterPotion(IDPotionMnd);

RegisterPotion(IDPotionSustaining, 1.1f);
RegisterPotion(IDPotionMax, 1.1f);
RegisterPotion(IDPotionEmpyrean, 1.1f);
RegisterPotion(IDPotionSuper, 1.1f);
RegisterPotion(IDPotionOrthos, 1.1f);
RegisterPotion(IDPotionHyper, 1.1f);

// special content actions - bozja, deep dungeons, etc
for (var i = BozjaHolsterID.None + 1; i < BozjaHolsterID.Count; ++i)
RegisterBozja(i);
Expand All @@ -245,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 Expand Up @@ -380,7 +410,7 @@ public void RegisterSpell<AID>(AID aid, bool isPhysRanged = false, float instant

private void Register(ActionID aid, ActionDefinition definition) => _definitions.Add(aid, definition);

private void RegisterPotion(ActionID aid)
private void RegisterPotion(ActionID aid, float animLock = 0.6f)
{
var baseId = aid.ID % 500000;
var item = ItemData(baseId);
Expand All @@ -399,6 +429,7 @@ private void RegisterPotion(ActionID aid)
CastTime = castTime,
MainCooldownGroup = cdgroup,
Cooldown = cooldown,
InstantAnimLock = animLock,
};
var aidHQ = new ActionID(ActionType.Item, baseId + 1000000);
_definitions[aidHQ] = new(aidHQ)
Expand All @@ -408,6 +439,7 @@ private void RegisterPotion(ActionID aid)
CastTime = castTime,
MainCooldownGroup = cdgroup,
Cooldown = cooldown * 0.9f,
InstantAnimLock = animLock
};
}

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;
}
}
34 changes: 34 additions & 0 deletions BossMod/ActionQueue/Melee/DRG.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,39 @@ 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
{
AID.ChaoticSpring => 0.45f, //Chaotic Spring delay
AID.HighJump => 0.49f, //High Jump delay
AID.CoerthanTorment => 0.49f, //Coerthan Torment delay
AID.BattleLitany => 0.62f, //Battle Litany delay
AID.LanceBarrage => 0.62f, //Lance Barrage delay
AID.FangAndClaw => 0.62f, //Fang and Claw delay
AID.RaidenThrust => 0.62f, //Raiden Thrust delay
AID.Geirskogul => 0.67f, //Geirskogul delay
AID.WheelingThrust => 0.67f, //Wheeling Thrust delay
AID.HeavensThrust => 0.71f, //Heavens Thrust delay
AID.DraconianFury => 0.76f, //Draconian Fury delay
AID.Nastrond => 0.76f, //Nastrond delay
AID.TrueThrust => 0.76f, //True Thrust delay
AID.DragonfireDive => 0.8f, //Dragonfire Dive delay
AID.MirageDive => 0.8f, //Mirage Dive delay
AID.SonicThrust => 0.8f, //Sonic Thrust delay
AID.PiercingTalon => 0.85f, //Piercing Talon delay
AID.Starcross => 0.98f, //Starcross delay
AID.VorpalThrust => 1.02f, //Vorpal Thrust delay
AID.RiseOfTheDragon => 1.16f, //Rise of the Dragon delay
AID.WyrmwindThrust => 1.2f, //Wyrmwind Thrust delay
AID.DoomSpike => 1.29f, //Doom Spike delay
AID.Stardiver => 1.29f, //Stardiver delay
AID.SpiralBlow => 1.38f, //Spiral Blow delay
AID.Disembowel => 1.65f, //Disembowel delay
AID.DragonsongDive => 2.23f, //Dragonsong Dive delay
_ => 0.0f
};

}
1 change: 1 addition & 0 deletions BossMod/ActionQueue/Melee/MNK.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public Definitions(ActionDefinitions d)
d.RegisterSpell(AID.ElixirBurst);
d.RegisterSpell(AID.WindsReply);
d.RegisterSpell(AID.FiresReply);
d.RegisterSpell(AID.EarthsReply);
d.RegisterSpell(AID.EnlightenedMeditation);
d.RegisterSpell(AID.ForbiddenMeditation);
d.RegisterSpell(AID.InspiritedMeditation);
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;
}
}
13 changes: 13 additions & 0 deletions BossMod/ActionQueue/Roleplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

public enum AID : uint
{
// palace of the dead/EO transformations
Pummel = 6273,
VoidFireII = 6274,
HeavenlyJudge = 6871,
Rotosmash = 32781,
WreckingBall = 32782,

// magitek reaper in Fly Free, My Pretty
MagitekCannon = 7619, // range 30 radius 6 ground targeted aoe
PhotonStream = 7620, // range 10 width 4 rect aoe
Expand Down Expand Up @@ -262,6 +269,12 @@ public sealed class Definitions : IDisposable
{
public Definitions(ActionDefinitions d)
{
d.RegisterSpell(AID.Pummel);
d.RegisterSpell(AID.VoidFireII);
d.RegisterSpell(AID.HeavenlyJudge);
d.RegisterSpell(AID.Rotosmash);
d.RegisterSpell(AID.WreckingBall);

d.RegisterSpell(AID.MagitekCannon);
d.RegisterSpell(AID.PhotonStream);
d.RegisterSpell(AID.DiffractiveMagitekCannon);
Expand Down
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;
}
}
4 changes: 4 additions & 0 deletions BossMod/ActionTweaks/ActionTweaksConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,9 @@ 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;

public bool ActivateAnticheat = true;
}
4 changes: 3 additions & 1 deletion BossMod/ActionTweaks/AutoAutosTweak.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ public bool GetDesiredState(bool currentState, ulong targetId)
if (_config.PyreticThreshold > 0 && hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && hints.ImminentSpecialMode.activation < ws.FutureTime(_config.PyreticThreshold))
return false; // pyretic => disable autos

if (hints.FindEnemy(target)?.Priority == AIHints.Enemy.PriorityForbidden)
var enemy = hints.FindEnemy(target);

if (enemy?.Priority == AIHints.Enemy.PriorityForbidden || enemy?.Spikes == true)
return false;

return player.InCombat || ws.Client.CountdownRemaining <= PrePullThreshold; // no reason not to enable autos!
Expand Down
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 @@ -29,7 +29,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
4 changes: 2 additions & 2 deletions BossMod/Autorotation/MiscAI/AutoFarm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public enum PriorityStrategy { None, Prioritize }

public static RotationModuleDefinition Definition()
{
RotationModuleDefinition res = new("Automatic targeting", "Collection of utilities to automatically target and pull mobs based on different criteria.", "AI", "veyn", RotationModuleQuality.Basic, new(~0ul), 1000, 1, RotationModuleOrder.HighLevel);
RotationModuleDefinition res = new("Automatic targeting", "Collection of utilities to automatically target and pull mobs based on different criteria.", "AI", "veyn", RotationModuleQuality.Basic, new(~0ul), 1000, 1, RotationModuleOrder.HighLevel, CanUseWhileRoleplaying: true);

res.Define(Track.General).As<GeneralStrategy>("General")
.AddOption(GeneralStrategy.FightBack, "FightBack", "Automatically engage any mobs that are in combat with player, but don't pull new mobs", supportedTargets: ActionTargets.Hostile)
Expand Down Expand Up @@ -58,7 +58,7 @@ void prioritize(AIHints.Enemy e, int prio)
// first deal with pulling new enemies
if (allowPulling)
{
if (World.Client.ActiveFate.ID != 0 && Player.Level <= Service.LuminaRow<Lumina.Excel.Sheets.Fate>(World.Client.ActiveFate.ID)?.ClassJobLevelMax && strategy.Option(Track.Fate).As<PriorityStrategy>() == PriorityStrategy.Prioritize)
if (Utils.IsPlayerSyncedToFate(World) && strategy.Option(Track.Fate).As<PriorityStrategy>() == PriorityStrategy.Prioritize)
{
foreach (var e in Hints.PotentialTargets)
{
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 are below 95% HP");

return def;
}

public override void Execute(StrategyValues strategy, 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 @@ -180,8 +180,13 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa
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
Loading

0 comments on commit 92fb71b

Please sign in to comment.