Skip to content

Commit

Permalink
Merge pull request #365 from FFXIV-CombatReborn/mergeWIP
Browse files Browse the repository at this point in the history
merge vbm
  • Loading branch information
CarnifexOptimus authored Sep 24, 2024
2 parents 8a1cb9c + 68d4abd commit 8661fa5
Show file tree
Hide file tree
Showing 34 changed files with 682 additions and 137 deletions.
6 changes: 6 additions & 0 deletions BossMod/AI/AIRotationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ public abstract class AIRotationModule(RotationModuleManager manager, Actor play
protected float Deadline(DateTime deadline) => Math.Max(0, (float)(deadline - Manager.WorldState.CurrentTime).TotalSeconds);
protected float Speed() => Player.FindStatus(50) != null ? 7.8f : 6;

protected bool InMeleeRange(Actor target)
{
var maxRange = target.HitboxRadius + Player.HitboxRadius + 3;
return (target.Position - Player.Position).LengthSq() < maxRange * maxRange;
}

protected void SetForcedMovement(WPos? pos, float tolerance = 0.1f)
{
var dir = (pos ?? Player.Position) - Player.Position;
Expand Down
4 changes: 4 additions & 0 deletions BossMod/ActionTweaks/ActionTweaksConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public enum ModifierKey
[PropertyDisplay("Automatically cancel a cast when target is dead")]
public bool CancelCastOnDeadTarget = false;

[PropertyDisplay("Prevent movement and action execution when pyretic-like mechanics are imminent (set to 0 to disable, otherwise increase threshold depending on your ping).")]
[PropertySlider(0, 10, Speed = 0.01f)]
public float PyreticThreshold = 1.0f;

[PropertyDisplay("Restore character orientation after action use (deprecated)", tooltip: "Note: this is deprecated in favour of smart character orientation and will be removed in future")]
public bool RestoreRotation = false;

Expand Down
5 changes: 4 additions & 1 deletion BossMod/ActionTweaks/CancelCastTweak.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// Utility for automatically cancelling casts in some conditions (when target dies, when ai wants it, etc).
// Since the game API is sending a packet, this implements some rate limiting.
public sealed class CancelCastTweak(WorldState ws)
public sealed class CancelCastTweak(WorldState ws, AIHints hints)
{
private readonly ActionTweaksConfig _config = Service.Config.Get<ActionTweaksConfig>();
private readonly WorldState _ws = ws;
Expand All @@ -22,6 +22,9 @@ public bool ShouldCancel(DateTime currentTime, bool force)

private bool WantCancel()
{
if (_config.PyreticThreshold > 0 && hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && hints.ImminentSpecialMode.activation < _ws.FutureTime(_config.PyreticThreshold))
return true;

if (!_config.CancelCastOnDeadTarget)
return false;

Expand Down
58 changes: 58 additions & 0 deletions BossMod/ActionTweaks/OutOfCombatActionsTweak.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
namespace BossMod;

[ConfigDisplay(Name = "Automatic out-of-combat utility actions", Parent = typeof(ActionTweaksConfig))]
class OutOfCombatActionsConfig : ConfigNode
{
[PropertyDisplay("Enable the feature")]
public bool Enabled = true;

[PropertyDisplay("Auto use Peloton when moving out of combat")]
public bool AutoPeloton = true;
}

// Tweak to automatically use out-of-combat convenience actions (peloton, pet summoning, etc).
public sealed class OutOfCombatActionsTweak : IDisposable
{
private readonly OutOfCombatActionsConfig _config = Service.Config.Get<OutOfCombatActionsConfig>();
private readonly WorldState _ws;
private readonly EventSubscriptions _subscriptions;
private DateTime _nextAutoPeloton;

public OutOfCombatActionsTweak(WorldState ws)
{
_ws = ws;
_subscriptions = new
(
ws.Actors.CastEvent.Subscribe(OnCastEvent)
);
}

public void Dispose()
{
_subscriptions.Dispose();
}

public void FillActions(Actor player, AIHints hints)
{
if (!_config.Enabled || player.InCombat || player.MountId != 0)
return;

if (_config.AutoPeloton && player.ClassCategory == ClassCategory.PhysRanged && player.Position != player.PrevPosition && _ws.CurrentTime >= _nextAutoPeloton && player.FindStatus(BRD.SID.Peloton) == null)
hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Peloton), player, ActionQueue.Priority.VeryLow);

// TODO: other things
}

private void OnCastEvent(Actor actor, ActorCastEvent evt)
{
if (actor != _ws.Party.Player())
return;

switch (evt.Action.ID)
{
case (uint)ClassShared.AID.Peloton:
_nextAutoPeloton = _ws.FutureTime(30);
break;
}
}
}
37 changes: 21 additions & 16 deletions BossMod/Autorotation/Standard/StandardWAR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,45 +275,50 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa
QueueGCD(WAR.AID.Tomahawk, primaryTarget, GCDPriority.ForcedTomahawk);

// oGCDs
var stratIR = strategy.Option(Track.InnerRelease);
if (InnerReleaseUnlocked)
{
if (ShouldUseInnerRelease(strategy.Option(Track.InnerRelease).As<OffensiveStrategy>(), primaryTarget))
QueueOGCD(WAR.AID.InnerRelease, Player, OGCDPriority.InnerRelease);
if (ShouldUseInnerRelease(stratIR.As<OffensiveStrategy>(), primaryTarget))
QueueOGCD(WAR.AID.InnerRelease, Player, stratIR.Value.PriorityOverride, OGCDPriority.InnerRelease);
}
else if (Unlocked(WAR.AID.Berserk))
{
if (ShouldUseBerserk(strategy.Option(Track.InnerRelease).As<OffensiveStrategy>(), primaryTarget, aoeTargets))
QueueOGCD(WAR.AID.Berserk, Player, OGCDPriority.InnerRelease);
if (ShouldUseBerserk(stratIR.As<OffensiveStrategy>(), primaryTarget, aoeTargets))
QueueOGCD(WAR.AID.Berserk, Player, stratIR.Value.PriorityOverride, OGCDPriority.InnerRelease);
}

if (Player.InCombat && Unlocked(WAR.AID.Infuriate))
{
var inf = ShouldUseInfuriate(strategy.Option(Track.Infuriate).As<InfuriateStrategy>(), primaryTarget);
var stratInf = strategy.Option(Track.Infuriate);
var inf = ShouldUseInfuriate(stratInf.As<InfuriateStrategy>(), primaryTarget);
if (inf.Use)
QueueOGCD(WAR.AID.Infuriate, Player, OGCDPriority.Infuriate, inf.Delayable ? ActionQueue.Priority.VeryLow : ActionQueue.Priority.Low);
QueueOGCD(WAR.AID.Infuriate, Player, stratInf.Value.PriorityOverride, OGCDPriority.Infuriate, inf.Delayable ? ActionQueue.Priority.VeryLow : ActionQueue.Priority.Low);
}

if (Unlocked(WAR.AID.Upheaval) && ShouldUseUpheaval(strategy.Option(Track.Upheaval).As<OffensiveStrategy>()))
var stratUph = strategy.Option(Track.Upheaval);
if (Unlocked(WAR.AID.Upheaval) && ShouldUseUpheaval(stratUph.As<OffensiveStrategy>()))
{
var aoe = aoeTargets >= 3 && Unlocked(WAR.AID.Orogeny);
QueueOGCD(aoe ? WAR.AID.Orogeny : WAR.AID.Upheaval, aoe ? Player : primaryTarget, OGCDPriority.Upheaval);
QueueOGCD(aoe ? WAR.AID.Orogeny : WAR.AID.Upheaval, aoe ? Player : primaryTarget, stratUph.Value.PriorityOverride, OGCDPriority.Upheaval);
}

if (aoeTargets > 0 && WrathfulLeft > World.Client.AnimationLock && ShouldUsePrimalWrath(strategy.Option(Track.Wrath).As<OffensiveStrategy>()))
var stratWrath = strategy.Option(Track.Wrath);
if (aoeTargets > 0 && WrathfulLeft > World.Client.AnimationLock && ShouldUsePrimalWrath(stratWrath.As<OffensiveStrategy>()))
{
QueueOGCD(WAR.AID.PrimalWrath, Player, OGCDPriority.PrimalWrath);
QueueOGCD(WAR.AID.PrimalWrath, Player, stratWrath.Value.PriorityOverride, OGCDPriority.PrimalWrath);
}

if (Unlocked(WAR.AID.Onslaught))
{
var onsStrategy = strategy.Option(Track.Onslaught).As<OnslaughtStrategy>();
if (ShouldUseOnslaught(onsStrategy, primaryTarget))
var stratOns = strategy.Option(Track.Onslaught);
var stratOnsOpt = stratOns.As<OnslaughtStrategy>();
if (ShouldUseOnslaught(stratOnsOpt, primaryTarget))
{
// special case for use as gapcloser - it has to be very high priority
var (prio, basePrio) = onsStrategy == OnslaughtStrategy.GapClose ? (OGCDPriority.GapcloseOnslaught, ActionQueue.Priority.High)
var (prio, basePrio) = stratOnsOpt == OnslaughtStrategy.GapClose ? (OGCDPriority.GapcloseOnslaught, ActionQueue.Priority.High)
: LostBloodRageStacks is > 0 and < 4 ? (OGCDPriority.LostBanner, ActionQueue.Priority.Medium)
: (OGCDPriority.Onslaught, OnslaughtCD < GCDLength ? ActionQueue.Priority.VeryLow : ActionQueue.Priority.Low);
QueueOGCD(WAR.AID.Onslaught, primaryTarget, prio, basePrio);
QueueOGCD(WAR.AID.Onslaught, primaryTarget, stratOns.Value.PriorityOverride, prio, basePrio);
}
}

Expand Down Expand Up @@ -344,11 +349,11 @@ private void QueueGCD(WAR.AID aid, Actor? target, GCDPriority prio)
}
}

private void QueueOGCD(WAR.AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Low)
private void QueueOGCD(WAR.AID aid, Actor? target, float prioOverride, OGCDPriority prio, float basePrio = ActionQueue.Priority.Low)
{
if (prio != OGCDPriority.None)
{
Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio);
Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, float.IsNaN(prioOverride) ? basePrio + (int)prio : prioOverride);
}
}

Expand Down
3 changes: 2 additions & 1 deletion BossMod/Autorotation/Strategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public OptionType As<OptionType>() where OptionType : Enum
return (OptionType)(object)Value.Option;
}

public float Priority() => float.IsNaN(Value.PriorityOverride) ? Config.Options[Value.Option].DefaultPriority : Value.PriorityOverride;
public float Priority(float defaultPrio) => float.IsNaN(Value.PriorityOverride) ? defaultPrio : Value.PriorityOverride;
public float Priority() => Priority(Config.Options[Value.Option].DefaultPriority);
}

public readonly OptionRef Option<TrackIndex>(TrackIndex index) where TrackIndex : Enum
Expand Down
118 changes: 118 additions & 0 deletions BossMod/Autorotation/Utility/ClassASTUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
namespace BossMod.Autorotation;

public sealed class ClassASTUtility(RotationModuleManager manager, Actor player) : RoleHealerUtility(manager, player)
{
public enum Track { Helios = SharedTrack.Count, Lightspeed, BeneficII, EssentialDignity, AspectedBenefic, AspectedHelios, Synastry, CollectiveUnconscious, CelestialOpposition, CelestialIntersection, Horoscope, NeutralSect, Exaltation, Macrocosmos, SunSign, Ascend, Play }
public enum HoroscopeOption { None, Use, End }
public enum MacrocosmosOption { None, Use, End }
public enum HeliosOption { None, Use, UseEx }
public enum CardsOption { None, PlayII, PlayIII }

public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(AST.AID.AstralStasis);

public static RotationModuleDefinition Definition()
{
var def = new RotationModuleDefinition("Utility: AST", "Planner support for utility actions", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.AST), 100);
DefineShared(def, IDLimitBreak3);

DefineSimpleConfig(def, Track.Helios, "Helios", "", 140, AST.AID.Helios);
DefineSimpleConfig(def, Track.Lightspeed, "Lightspeed", "L.Speed", 140, AST.AID.Lightspeed);
DefineSimpleConfig(def, Track.BeneficII, "BeneficII", "Bene2", 100, AST.AID.BeneficII);
DefineSimpleConfig(def, Track.EssentialDignity, "EssentialDignity", "E.Dig", 140, AST.AID.EssentialDignity);
DefineSimpleConfig(def, Track.AspectedBenefic, "AspectedBenefic", "A.Benefic", 100, AST.AID.AspectedBenefic, 15);

def.Define(Track.AspectedHelios).As<HeliosOption>("AspectedHelios", "A.Helios", 130)
.AddOption(HeliosOption.None, "None", "Do not use automatically")
.AddOption(HeliosOption.Use, "Use", "Use Aspected Helios", 1, 15, ActionTargets.Self, 40, 95)
.AddOption(HeliosOption.UseEx, "UseEx", "Use Helios Conjunction", 1, 15, ActionTargets.Self, 96)
.AddAssociatedActions(AST.AID.AspectedHelios, AST.AID.HeliosConjunction);

DefineSimpleConfig(def, Track.Synastry, "Synastry", "", 200, AST.AID.Synastry, 20);
DefineSimpleConfig(def, Track.CollectiveUnconscious, "CollectiveUnconscious", "C.Uncon", 100, AST.AID.CollectiveUnconscious, 5); //15s regen also
DefineSimpleConfig(def, Track.CelestialOpposition, "CelestialOpposition", "C.Oppo", 100, AST.AID.CelestialOpposition, 15);
DefineSimpleConfig(def, Track.CelestialIntersection, "CelestialIntersection", "C.Inter", 100, AST.AID.CelestialIntersection, 30);

def.Define(Track.Horoscope).As<HoroscopeOption>("Horoscope", "Horo", 130)
.AddOption(HoroscopeOption.None, "None", "Do not use automatically")
.AddOption(HoroscopeOption.Use, "Use", "Use Horoscope", 60, 10, ActionTargets.Self, 76)
.AddOption(HoroscopeOption.End, "UseEx", "End Horoscope", 0, 1, ActionTargets.Self, 76)
.AddAssociatedActions(AST.AID.Horoscope, AST.AID.HoroscopeEnd);

DefineSimpleConfig(def, Track.NeutralSect, "NeutralSect", "Sect", 250, AST.AID.NeutralSect, 30);
DefineSimpleConfig(def, Track.Exaltation, "Exaltation", "Exalt", 100, AST.AID.Exaltation, 8);

def.Define(Track.Macrocosmos).As<MacrocosmosOption>("Macrocosmos", "Macro", 300)
.AddOption(MacrocosmosOption.None, "None", "Do not use automatically")
.AddOption(MacrocosmosOption.Use, "Use", "Use Macrocosmos", 120, 2, ActionTargets.Hostile, 90)
.AddOption(MacrocosmosOption.End, "UseEx", "Use Microcosmos", 0, 1, ActionTargets.Hostile, 90)
.AddAssociatedActions(AST.AID.Macrocosmos, AST.AID.MicrocosmosEnd);

DefineSimpleConfig(def, Track.SunSign, "SunSign", "", 290, AST.AID.SunSign);
DefineSimpleConfig(def, Track.Ascend, "Ascend", "Raise", 10, AST.AID.Ascend, 7);

def.Define(Track.Play).As<CardsOption>("Play", "Play", 130)
.AddOption(CardsOption.None, "None", "Do not use automatically")
.AddOption(CardsOption.PlayII, "PlayII", "Use Play II's Card", 1, 15, ActionTargets.Self | ActionTargets.Party, 30)
.AddOption(CardsOption.PlayIII, "PlayIII", "Use Play III's Card", 1, 15, ActionTargets.Self | ActionTargets.Party, 30)
.AddAssociatedActions(AST.AID.PlayII, AST.AID.PlayIII);

return def;
}

public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, float forceMovementIn, bool isMoving)
{
ExecuteShared(strategy, IDLimitBreak3);
ExecuteSimple(strategy.Option(Track.Lightspeed), AST.AID.Lightspeed, Player);
ExecuteSimple(strategy.Option(Track.BeneficII), AST.AID.BeneficII, primaryTarget);
ExecuteSimple(strategy.Option(Track.EssentialDignity), AST.AID.EssentialDignity, primaryTarget);
ExecuteSimple(strategy.Option(Track.AspectedBenefic), AST.AID.AspectedBenefic, primaryTarget);
ExecuteSimple(strategy.Option(Track.Synastry), AST.AID.Synastry, primaryTarget);
ExecuteSimple(strategy.Option(Track.CollectiveUnconscious), AST.AID.CollectiveUnconscious, Player);
ExecuteSimple(strategy.Option(Track.CelestialOpposition), AST.AID.CelestialOpposition, Player);
ExecuteSimple(strategy.Option(Track.CelestialIntersection), AST.AID.CelestialIntersection, Player);
ExecuteSimple(strategy.Option(Track.NeutralSect), AST.AID.NeutralSect, Player);
ExecuteSimple(strategy.Option(Track.Exaltation), AST.AID.Exaltation, primaryTarget);
ExecuteSimple(strategy.Option(Track.SunSign), AST.AID.SunSign, Player);
ExecuteSimple(strategy.Option(Track.Ascend), AST.AID.Ascend, primaryTarget);

var helios = strategy.Option(Track.Macrocosmos);
var heliosAction = helios.As<HeliosOption>() switch
{
HeliosOption.Use => AST.AID.AspectedHelios,
HeliosOption.UseEx => AST.AID.HeliosConjunction,
_ => default
};
if (heliosAction != default)
Hints.ActionsToExecute.Push(ActionID.MakeSpell(heliosAction), Player, helios.Priority(), helios.Value.ExpireIn);

var horo = strategy.Option(Track.Horoscope);
var horoAction = horo.As<HoroscopeOption>() switch
{
HoroscopeOption.Use => AST.AID.Horoscope,
HoroscopeOption.End => AST.AID.HoroscopeEnd,
_ => default
};
if (horoAction != default)
Hints.ActionsToExecute.Push(ActionID.MakeSpell(horoAction), Player, horo.Priority(), horo.Value.ExpireIn);

var cosmos = strategy.Option(Track.Macrocosmos);
var cosmosAction = cosmos.As<MacrocosmosOption>() switch
{
MacrocosmosOption.Use => AST.AID.Macrocosmos,
MacrocosmosOption.End => AST.AID.MicrocosmosEnd,
_ => default
};
if (cosmosAction != default)
Hints.ActionsToExecute.Push(ActionID.MakeSpell(cosmosAction), Player, cosmos.Priority(), cosmos.Value.ExpireIn);

var cards = strategy.Option(Track.Play);
var cardsAction = cards.As<CardsOption>() switch
{
CardsOption.PlayII => AST.AID.PlayII,
CardsOption.PlayIII => AST.AID.PlayIII,
_ => default
};
if (cardsAction != default)
Hints.ActionsToExecute.Push(ActionID.MakeSpell(cardsAction), Player, cards.Priority(), cards.Value.ExpireIn);
}
}
Loading

0 comments on commit 8661fa5

Please sign in to comment.