From e58655d305dda78ac1f18e753b01fa37aa60056b Mon Sep 17 00:00:00 2001 From: Thomas Brooks <34015422+NostraThomas99@users.noreply.github.com> Date: Sun, 15 Dec 2024 05:37:50 -0600 Subject: [PATCH] Basic BLU Rotation Added "Basic BLU" rotation, a blue mage rotation that's actually sort of useful. Swap to BLU and see the rotation configuration menu for more information. Misc improvements to core functionality to support Basic BLU --- BasicRotations/Limited Jobs/BLU_Basic.cs | 174 ++++++++++++++++++ .../Actions/ActionBasicInfo.cs | 22 +-- .../Rotations/Basic/BlueMageRotation.cs | 92 ++++++++- RotationSolver/Helpers/DownloadHelper.cs | 3 +- RotationSolver/Helpers/RotationLoadContext.cs | 1 + RotationSolver/UI/RotationConfigWindow.cs | 1 + RotationSolver/Updaters/PreviewUpdater.cs | 15 ++ 7 files changed, 286 insertions(+), 22 deletions(-) create mode 100644 BasicRotations/Limited Jobs/BLU_Basic.cs diff --git a/BasicRotations/Limited Jobs/BLU_Basic.cs b/BasicRotations/Limited Jobs/BLU_Basic.cs new file mode 100644 index 000000000..07b62767e --- /dev/null +++ b/BasicRotations/Limited Jobs/BLU_Basic.cs @@ -0,0 +1,174 @@ +namespace DefaultRotations.Magical; + +[Rotation("Basic BLU", CombatType.PvE, GameVersion = "7.11")] +[SourceCode(Path = "main/BasicRotations/Limited Jobs/BLU_Basic.cs")] +[Api(4)] +public sealed class Blue_Basic : BlueMageRotation +{ + + [RotationConfig(CombatType.PvE, Name = "Single Target Spell")] + public BluDPSSpell SingleTargetDPSSpell { get; set; } = BluDPSSpell.SonicBoom; + + [RotationConfig(CombatType.PvE, Name = "AoE Spell")] + public BluAOESpell AoeSpell { get; set; } = BluAOESpell.MindBlast; + + [RotationConfig(CombatType.PvE, Name = "Healing Spell")] + public BluHealSpell HealSpell { get; set; } = BluHealSpell.WhiteWind; + + [RotationConfig(CombatType.PvE, Name = "Use Basic Instinct")] + public bool UseBasicInstinct { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Mighty Guard")] + public bool UseMightyGuard { get; set; } = true; + + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + + return base.CountDownAction(remainTime); + } + #endregion + + #region Emergency Logic + // Determines emergency actions to take based on the next planned GCD action. + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + + return base.EmergencyAbility(nextGCD, out act); + } + #endregion + + #region Move oGCD Logic + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.MoveForwardAbility(nextGCD, out act); + } + + protected override bool MoveBackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.MoveBackAbility(nextGCD, out act); + } + + protected override bool SpeedAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.SpeedAbility(nextGCD, out act); + } + #endregion + + #region Heal/Defense oGCD Logic + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.HealSingleAbility(nextGCD, out act); + } + + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.DefenseAreaAbility(nextGCD, out act); + } + + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.DefenseSingleAbility(nextGCD, out act); + } + #endregion + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (Player.CurrentMp < 6000 && InCombat && LucidDreamingPvE.CanUse(out act)) return true; + //if (AethericMimicryPvE_19239.CanUse(out act)) return true; + return base.GeneralAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + + protected override bool EmergencyGCD(out IAction? act) + { + return base.EmergencyGCD(out act); + } + + protected override bool MyInterruptGCD(out IAction? act) + { + if (FlyingSardinePvE.CanUse(out act)) return true; + return base.MyInterruptGCD(out act); + } + + protected override bool DefenseAreaGCD(out IAction? act) + { + //if (ColdFogPvE.CanUse(out act)) return true; + return base.DefenseAreaGCD(out act); + } + + protected override bool DefenseSingleGCD(out IAction? act) + { + return base.DefenseSingleGCD(out act); + } + + protected override bool HealAreaGCD(out IAction? act) + { + if (BluHealSpellActions[HealSpell].CanUse(out act)) return true; + return base.HealAreaGCD(out act); + } + + protected override bool HealSingleGCD(out IAction? act) + { + if (BluHealSpellActions[HealSpell].CanUse(out act)) return true; + return base.HealSingleGCD(out act); + } + + protected override bool MoveForwardGCD(out IAction? act) + { + return base.MoveForwardGCD(out act); + } + + protected override bool DispelGCD(out IAction? act) + { + return base.DispelGCD(out act); + } + + protected override bool RaiseGCD(out IAction? act) + { + //if (AngelWhisperPvE.CanUse(out act)) return true; + return base.RaiseGCD(out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + if (UseMightyGuard && MightyGuardPvE.CanUse(out act)) return true; + if (UseBasicInstinct && BasicInstinctPvE.CanUse(out act)) return true; + if (BluAOESpellActions[AoeSpell].CanUse(out act)) return true; + if (BluDPSSpellActions[SingleTargetDPSSpell].CanUse(out act)) return true; + if (FlyingSardinePvE.CanUse(out act)) return true; + return base.GeneralGCD(out act); + } + #endregion +} \ No newline at end of file diff --git a/RotationSolver.Basic/Actions/ActionBasicInfo.cs b/RotationSolver.Basic/Actions/ActionBasicInfo.cs index a8ad3da6c..15445a3a9 100644 --- a/RotationSolver.Basic/Actions/ActionBasicInfo.cs +++ b/RotationSolver.Basic/Actions/ActionBasicInfo.cs @@ -1,6 +1,7 @@ using ECommons.ExcelServices; using ECommons.GameHelpers; using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.UI; namespace RotationSolver.Basic.Actions; @@ -150,9 +151,7 @@ internal readonly bool BasicCheck(bool skipStatusProvideCheck, bool skipComboChe { if (!IsActionEnabled() || !IsOnSlot) return false; if (IsLimitBreak) return true; - if (IsActionDisabled() || !EnoughLevel || !HasEnoughMP() || !IsQuestUnlocked()) return false; - - var player = Player.Object; + if (IsActionDisabled() || !EnoughLevel || !HasEnoughMP() || !SpellUnlocked) return false; if (IsStatusNeeded() || IsStatusProvided(skipStatusProvideCheck)) return false; if (IsLimitBreakLevelLow() || !IsComboValid(skipComboCheck) || !IsRoleActionValid()) return false; @@ -163,24 +162,17 @@ internal readonly bool BasicCheck(bool skipStatusProvideCheck, bool skipComboChe return true; } + /// + /// Whether the spell is unlocked by the player + /// + public unsafe bool SpellUnlocked => _action.Action.UnlockLink.RowId <= 0 || UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(_action.Action.UnlockLink.RowId); + private bool IsActionEnabled() => _action.Config?.IsEnabled ?? false; private bool IsActionDisabled() => !IBaseAction.ForceEnable && (DataCenter.DisabledActionSequencer?.Contains(ID) ?? false); private bool HasEnoughMP() => DataCenter.CurrentMp >= MPNeed; - private bool IsQuestUnlocked() - { - if (_action.Setting.UnlockedByQuestID == 0) return true; - var isUnlockQuestComplete = QuestManager.IsQuestComplete(_action.Setting.UnlockedByQuestID); - if (!isUnlockQuestComplete) - { - var warning = $"The action {Name} is locked by the quest {_action.Setting.UnlockedByQuestID}. Please complete this quest to learn this action."; - WarningHelper.AddSystemWarning(warning); - } - return isUnlockQuestComplete; - } - private bool IsStatusNeeded() { var player = Player.Object; diff --git a/RotationSolver.Basic/Rotations/Basic/BlueMageRotation.cs b/RotationSolver.Basic/Rotations/Basic/BlueMageRotation.cs index ce3d91aab..0035fd67a 100644 --- a/RotationSolver.Basic/Rotations/Basic/BlueMageRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/BlueMageRotation.cs @@ -1,7 +1,58 @@ -namespace RotationSolver.Basic.Rotations.Basic; +using ECommons; + +namespace RotationSolver.Basic.Rotations.Basic; partial class BlueMageRotation { + public enum BluDPSSpell : byte + { + WaterCannon, + SonicBoom, + GoblinPunch, + + } + + public enum BluAOESpell : byte + { + Glower, + FlyingFrenzy, + FlameThrower, + DrillCannons, + Plaincracker, + HighVoltage, + MindBlast, + ThousandNeedles, + } + + public enum BluHealSpell : byte + { + WhiteWind, + AngelsSnack, + } + + public BlueMageRotation() + { + BluDPSSpellActions.Add(BluDPSSpell.WaterCannon, WaterCannonPvE); + BluDPSSpellActions.Add(BluDPSSpell.SonicBoom, SonicBoomPvE); + BluDPSSpellActions.Add(BluDPSSpell.GoblinPunch, GoblinPunchPvE); + + BluHealSpellActions.Add(BluHealSpell.WhiteWind, WhiteWindPvE); + BluHealSpellActions.Add(BluHealSpell.AngelsSnack, AngelsSnackPvE); + + BluAOESpellActions.Add(BluAOESpell.Glower, GlowerPvE); + BluAOESpellActions.Add(BluAOESpell.FlyingFrenzy, FlyingFrenzyPvE); + BluAOESpellActions.Add(BluAOESpell.FlameThrower, FlameThrowerPvE); + BluAOESpellActions.Add(BluAOESpell.DrillCannons, DrillCannonsPvE); + BluAOESpellActions.Add(BluAOESpell.Plaincracker, PlaincrackerPvE); + BluAOESpellActions.Add(BluAOESpell.HighVoltage, HighVoltagePvE); + BluAOESpellActions.Add(BluAOESpell.MindBlast, MindBlastPvE); + BluAOESpellActions.Add(BluAOESpell.ThousandNeedles, _1000NeedlesPvE); + } + + public Dictionary BluDPSSpellActions = []; + public Dictionary BluAOESpellActions = []; + public Dictionary BluHealSpellActions = []; + /// /// /// @@ -30,10 +81,8 @@ public enum BLUID : byte DPS, } - /// - /// - /// - protected static BLUID BlueId { get; set; } = BLUID.DPS; + [RotationConfig(CombatType.PvE, Name = "Aetheric Mimicry Role")] + public static BLUID BlueId { get; set; } = BLUID.DPS; static partial void ModifySongOfTormentPvE(ref ActionSetting setting) { @@ -214,6 +263,12 @@ static partial void ModifySeaShantyPvE(ref ActionSetting setting) } + static partial void ModifyMightyGuardPvE(ref ActionSetting setting) + { + setting.IsFriendly = true; + setting.StatusProvide = [StatusID.MightyGuard]; + } + static partial void ModifyBeingMortalPvE(ref ActionSetting setting) { setting.IsFriendly = false; @@ -223,6 +278,15 @@ static partial void ModifyBeingMortalPvE(ref ActionSetting setting) }; } + static partial void ModifyGlowerPvE(ref ActionSetting setting) + { + setting.IsFriendly = false; + setting.CreateConfig = () => new ActionConfig() + { + AoeCount = 1, + }; + } + //Optional static partial void ModifyFlyingSardinePvE(ref ActionSetting setting) @@ -230,11 +294,27 @@ static partial void ModifyFlyingSardinePvE(ref ActionSetting setting) } + static partial void ModifyMindBlastPvE(ref ActionSetting setting) + { + setting.CreateConfig = () => new ActionConfig() + { + AoeCount = 3, + }; + setting.IsFriendly = false; + } + static partial void ModifyWhiteWindPvE(ref ActionSetting setting) { setting.IsFriendly = true; } + static partial void ModifyBasicInstinctPvE(ref ActionSetting setting) + { + setting.IsFriendly = true; + setting.StatusProvide = [StatusID.BasicInstinct]; + setting.ActionCheck = () => IsInDuty && PartyMembers.Count() <= 1 && DataCenter.TerritoryContentType != TerritoryContentType.TheMaskedCarnivale; + } + /// /// /// @@ -353,7 +433,7 @@ public override void DisplayStatus() // public static IBLUAction FireAngon { get; } = new BLUAction(ActionID.FireAngon); // /// -// /// +// /// // /// // public static IBLUAction MindBlast { get; } = new BLUAction(ActionID.MindBlast); diff --git a/RotationSolver/Helpers/DownloadHelper.cs b/RotationSolver/Helpers/DownloadHelper.cs index 33062e166..d4f6ddfae 100644 --- a/RotationSolver/Helpers/DownloadHelper.cs +++ b/RotationSolver/Helpers/DownloadHelper.cs @@ -1,4 +1,5 @@ -using RotationSolver.UI; +using ECommons.DalamudServices; +using RotationSolver.UI; namespace RotationSolver.Helpers; diff --git a/RotationSolver/Helpers/RotationLoadContext.cs b/RotationSolver/Helpers/RotationLoadContext.cs index 2cb83af9f..5f6f983e9 100644 --- a/RotationSolver/Helpers/RotationLoadContext.cs +++ b/RotationSolver/Helpers/RotationLoadContext.cs @@ -2,6 +2,7 @@ using Lumina.Excel; //using Lumina.Excel.CustomSheets; using System.Runtime.Loader; +using ECommons.DalamudServices; namespace RotationSolver.Helpers; diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 0835a6ef0..a9ee8b160 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -1634,6 +1634,7 @@ static void DrawActionDebug() ImGui.Text($"Can Use: {action.CanUse(out _)} "); ImGui.Text("IgnoreCastCheck:" + action.CanUse(out _, skipCastingCheck: true).ToString()); ImGui.Text("Target Name: " + action.Target.Target?.Name ?? string.Empty); + ImGui.Text($"SpellUnlocked: {action.Info.SpellUnlocked} ({action.Action.UnlockLink.RowId})"); } catch { diff --git a/RotationSolver/Updaters/PreviewUpdater.cs b/RotationSolver/Updaters/PreviewUpdater.cs index e61424571..2fc415ca4 100644 --- a/RotationSolver/Updaters/PreviewUpdater.cs +++ b/RotationSolver/Updaters/PreviewUpdater.cs @@ -43,6 +43,7 @@ private static void UpdateEntry() Job.SMN => BitmapFontIcon.Summoner, Job.RDM => BitmapFontIcon.RedMage, Job.PCT => BitmapFontIcon.Pictomancer, + Job.BLU => BitmapFontIcon.BlueMage, Job.MNK => BitmapFontIcon.Monk, Job.SAM => BitmapFontIcon.Samurai, @@ -54,6 +55,20 @@ private static void UpdateEntry() Job.BRD => BitmapFontIcon.Bard, Job.MCH => BitmapFontIcon.Machinist, Job.DNC => BitmapFontIcon.Dancer, + + Job.BSM => BitmapFontIcon.Blacksmith, + Job.ARM => BitmapFontIcon.Armorer, + Job.WVR => BitmapFontIcon.Weaver, + Job.ALC => BitmapFontIcon.Alchemist, + Job.CRP => BitmapFontIcon.Carpenter, + Job.LTW => BitmapFontIcon.Leatherworker, + Job.CUL => BitmapFontIcon.Culinarian, + Job.GSM => BitmapFontIcon.Goldsmith, + + Job.FSH => BitmapFontIcon.Fisher, + Job.MIN => BitmapFontIcon.Miner, + Job.BTN => BitmapFontIcon.Botanist, + _ => BitmapFontIcon.ExclamationRectangle, };