├── .gitignore
├── res
├── icon.png
├── eno_swap.gif
├── souleater_combo.gif
└── hypercharge_heat_blast.gif
├── .gitmodules
├── .editorconfig
├── XIVComboExpanded
├── Attributes
│ ├── SecretCustomComboAttribute.cs
│ ├── ParentComboAttribute.cs
│ ├── ConflictingCombosAttribute.cs
│ └── CustomComboInfoAttribute.cs
├── XIVComboExpanded.json
├── Combos
│ ├── DOH.cs
│ ├── BLU.cs
│ ├── ADV.cs
│ ├── DOL.cs
│ ├── WHM.cs
│ ├── AST.cs
│ ├── SCH.cs
│ ├── DRK.cs
│ ├── GNB.cs
│ ├── SGE.cs
│ ├── MCH.cs
│ ├── SMN.cs
│ ├── WAR.cs
│ ├── DNC.cs
│ ├── NIN.cs
│ ├── DRG.cs
│ ├── SAM.cs
│ └── MNK.cs
├── PluginAddressResolver.cs
├── GlobalSuppressions.cs
├── XIVComboExpanded.csproj
├── Service.cs
├── IconReplacer.cs
├── CooldownData.cs
├── PluginConfiguration.cs
├── CustomComboCache.cs
├── Interface
│ └── ConfigWindow.cs
└── XIVComboExpandedPlugin.cs
├── stylecop.json
├── README.md
├── XIVComboExpanded.sln
├── .github
└── workflows
│ └── build.yml
└── statement.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | bin/
3 | obj/
4 | /lib/
5 |
--------------------------------------------------------------------------------
/res/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyvorld/XIVComboPlugin/master/res/icon.png
--------------------------------------------------------------------------------
/res/eno_swap.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyvorld/XIVComboPlugin/master/res/eno_swap.gif
--------------------------------------------------------------------------------
/res/souleater_combo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyvorld/XIVComboPlugin/master/res/souleater_combo.gif
--------------------------------------------------------------------------------
/res/hypercharge_heat_blast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andyvorld/XIVComboPlugin/master/res/hypercharge_heat_blast.gif
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/FFXIVClientStructs"]
2 | path = lib/FFXIVClientStructs
3 | url = https://github.com/aers/FFXIVClientStructs.git
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{cs,vb}]
2 |
3 | # IDE0003: Remove qualification
4 | dotnet_style_qualification_for_event =true:silent
5 | dotnet_style_qualification_for_field=true:silent
6 | dotnet_style_qualification_for_property=true:silent
7 | dotnet_style_qualification_for_method=true:silent
8 | dotnet_diagnostic.CA1822.severity=silent
9 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Attributes/SecretCustomComboAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace XIVComboExpandedPlugin.Attributes;
4 |
5 | ///
6 | /// Attribute designating secret combos.
7 | ///
8 | [AttributeUsage(AttributeTargets.Field)]
9 | internal class SecretCustomComboAttribute : Attribute
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "orderingRules": {
5 | "systemUsingDirectivesFirst": true,
6 | "usingDirectivesPlacement": "outsideNamespace",
7 | "blankLinesBetweenUsingGroups": "require"
8 | },
9 | "maintainabilityRules": {
10 | "topLevelTypes": [ "class", "interface", "struct", "enum" ]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/XIVComboExpanded/XIVComboExpanded.json:
--------------------------------------------------------------------------------
1 | {
2 | "Author": "attick, daemitus, Khayle",
3 | "Name": "XIV Combo Expanded",
4 | "Punchline": "Condenses combos and mutually exclusive abilities onto a single button.",
5 | "Description": "This plugin condenses combos and mutually exclusive abilities onto a single button. For example, a 1-2-3 combo would become 1-1-1.",
6 | "RepoUrl": "https://github.com/daemitus/XIVComboPlugin",
7 | "IconUrl": "https://raw.githubusercontent.com/daemitus/XIVComboPlugin/master/res/icon.png",
8 | "ImageUrls": [],
9 | "DalamudApiLevel": 9
10 | }
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/DOH.cs:
--------------------------------------------------------------------------------
1 | namespace XIVComboExpandedPlugin.Combos;
2 |
3 | internal static class DOH
4 | {
5 | public const byte ClassID = 0;
6 | public const byte JobID = 50;
7 |
8 | public const uint
9 | Placeholder = 0;
10 |
11 | public static class Buffs
12 | {
13 | public const ushort
14 | Placeholder = 0;
15 | }
16 |
17 | public static class Debuffs
18 | {
19 | public const ushort
20 | Placeholder = 0;
21 | }
22 |
23 | public static class Levels
24 | {
25 | public const byte
26 | Placeholder = 0;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/BLU.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class BLU
6 | {
7 | public const byte JobID = 36;
8 |
9 | public const uint
10 | AngelWhisper = 18317;
11 |
12 | public static class Buffs
13 | {
14 | public const ushort
15 | Placeholder = 0;
16 | }
17 |
18 | public static class Debuffs
19 | {
20 | public const ushort
21 | Placeholder = 0;
22 | }
23 |
24 | public static class Levels
25 | {
26 | public const byte
27 | AngelWhisper = 1;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Attributes/ParentComboAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace XIVComboExpandedPlugin.Attributes;
4 |
5 | ///
6 | /// Attribute documenting required combo relationships.
7 | ///
8 | [AttributeUsage(AttributeTargets.Field)]
9 | internal class ParentComboAttribute : Attribute
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// Presets that conflict with the given combo.
15 | internal ParentComboAttribute(CustomComboPreset parentPreset)
16 | {
17 | this.ParentPreset = parentPreset;
18 | }
19 |
20 | ///
21 | /// Gets the display name.
22 | ///
23 | public CustomComboPreset ParentPreset { get; }
24 | }
25 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Attributes/ConflictingCombosAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace XIVComboExpandedPlugin.Attributes;
4 |
5 | ///
6 | /// Attribute documenting conflicting presets for each combo.
7 | ///
8 | [AttributeUsage(AttributeTargets.Field)]
9 | internal class ConflictingCombosAttribute : Attribute
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// Presets that conflict with the given combo.
15 | internal ConflictingCombosAttribute(params CustomComboPreset[] conflictingPresets)
16 | {
17 | this.ConflictingPresets = conflictingPresets;
18 | }
19 |
20 | ///
21 | /// Gets the display name.
22 | ///
23 | public CustomComboPreset[] ConflictingPresets { get; }
24 | }
25 |
--------------------------------------------------------------------------------
/XIVComboExpanded/PluginAddressResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Dalamud.Game;
4 | using Dalamud.Logging;
5 |
6 | namespace XIVComboExpandedPlugin;
7 |
8 | ///
9 | /// Plugin address resolver.
10 | ///
11 | internal class PluginAddressResolver : BaseAddressResolver
12 | {
13 | ///
14 | /// Gets the address of the member ComboTimer.
15 | ///
16 | public IntPtr ComboTimer { get; private set; }
17 |
18 | ///
19 | /// Gets the address of the member LastComboMove.
20 | ///
21 | public IntPtr LastComboMove => this.ComboTimer + 0x4;
22 |
23 | ///
24 | /// Gets the address of fpGetAdjustedActionId.
25 | ///
26 | public IntPtr GetAdjustedActionId { get; private set; }
27 |
28 | ///
29 | /// Gets the address of fpIsIconReplacable.
30 | ///
31 | public IntPtr IsActionIdReplaceable { get; private set; }
32 |
33 | ///
34 | protected override void Setup64Bit(ISigScanner scanner)
35 | {
36 | this.ComboTimer = scanner.GetStaticAddressFromSig("F3 0F 11 05 ?? ?? ?? ?? 48 83 C7 08");
37 |
38 | this.GetAdjustedActionId = scanner.ScanText("E8 ?? ?? ?? ?? 89 03 8B 03"); // Client::Game::ActionManager.GetAdjustedActionId
39 |
40 | this.IsActionIdReplaceable = scanner.ScanText("E8 ?? ?? ?? ?? 84 C0 74 4C 8B D3");
41 |
42 | PluginLog.Verbose("===== X I V C O M B O =====");
43 | PluginLog.Verbose($"{nameof(this.GetAdjustedActionId)} 0x{this.GetAdjustedActionId:X}");
44 | PluginLog.Verbose($"{nameof(this.IsActionIdReplaceable)} 0x{this.IsActionIdReplaceable:X}");
45 | PluginLog.Verbose($"{nameof(this.ComboTimer)} 0x{this.ComboTimer:X}");
46 | PluginLog.Verbose($"{nameof(this.LastComboMove)} 0x{this.LastComboMove:X}");
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XIVComboPlugin
2 |
3 | [](https://github.com/daemitus/XIVComboPlugin/actions/workflows/build.yml)
4 |
5 | This plugin condenses combos and mutually exclusive abilities onto a single button. Thanks to Meli for the initial start, and obviously goat for making any of this possible.
6 |
7 | ## About
8 | XIVCombo is a plugin to allow for "one-button" combo chains, as well as implementing various other mutually-exclusive button consolidation and quality of life replacements.
9 |
10 | For some jobs, this frees a massive amount of hotbar space (looking at you, DRG). For most, it removes a lot of mindless tedium associated with having to press various buttons that have little logical reason to be separate.
11 |
12 | ## Installation
13 | * Type `/xlplugins` in-game to access the plugin installer and updater. Any releases on this github page have been removed to facilitate proper installation going forward.
14 | ## In-game usage
15 | * Type `/pcombo` to pull up a GUI for editing active combo replacements.
16 | * Drag the named ability from your ability list onto your hotbar to use.
17 | * For example, to use DRK's Souleater combo, first check the box, then place Souleater on your bar. It should automatically turn into Hard Slash.
18 | * The description associated with each combo should be enough to tell you which ability needs to be placed on the hotbar.
19 | ### Examples
20 | 
21 | 
22 | 
23 |
24 | ## Known Issues
25 | * None, for now!
26 |
27 | ## Full list of supported combos
28 | There used to be a list here, but it got annoying to update. You should install it and check out what's available!
29 |
30 | Place `https://github.com/daemitus/MyDalamudPlugins/raw/master/pluginmaster.json` in your /xlsettings 3rd party repo list to access this plugin.
31 |
--------------------------------------------------------------------------------
/XIVComboExpanded.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31423.177
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XIVComboExpanded", "XIVComboExpanded\XIVComboExpanded.csproj", "{619DF476-7225-4783-95A5-D29A8816EE66}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8948C02A-914E-4A51-B1FE-2C608492EBF5}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | .gitignore = .gitignore
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Github Actions", "Github Actions", "{D3F73229-7C85-4017-B8C9-B7668022A4F0}"
16 | ProjectSection(SolutionItems) = preProject
17 | .github\workflows\build.yml = .github\workflows\build.yml
18 | EndProjectSection
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Debug|x64 = Debug|x64
24 | Release|Any CPU = Release|Any CPU
25 | Release|x64 = Release|x64
26 | EndGlobalSection
27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 | {619DF476-7225-4783-95A5-D29A8816EE66}.Debug|Any CPU.ActiveCfg = Debug|x64
29 | {619DF476-7225-4783-95A5-D29A8816EE66}.Debug|Any CPU.Build.0 = Debug|x64
30 | {619DF476-7225-4783-95A5-D29A8816EE66}.Debug|x64.ActiveCfg = Debug|x64
31 | {619DF476-7225-4783-95A5-D29A8816EE66}.Debug|x64.Build.0 = Debug|x64
32 | {619DF476-7225-4783-95A5-D29A8816EE66}.Release|Any CPU.ActiveCfg = Release|x64
33 | {619DF476-7225-4783-95A5-D29A8816EE66}.Release|Any CPU.Build.0 = Release|x64
34 | {619DF476-7225-4783-95A5-D29A8816EE66}.Release|x64.ActiveCfg = Release|x64
35 | {619DF476-7225-4783-95A5-D29A8816EE66}.Release|x64.Build.0 = Release|x64
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {37674B88-2038-4C63-A979-84404391773A}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | # Put your personal access token in a repository secret named PAT for cross-repository access
4 |
5 | on:
6 | workflow_dispatch:
7 | push:
8 | branches:
9 | - master
10 |
11 | env:
12 | PUBLIC_NAME: XIVComboExpanded
13 | SOLUTION_NAME: XIVComboExpanded
14 | INTERNAL_NAME: XIVComboExpanded
15 | RELEASE_DIR: XIVComboExpanded\bin\Release\XIVComboExpanded
16 | PERSONAL_PLUGIN_REPO: daemitus/MyDalamudPlugins
17 | PR_PLUGIN_REPO: daemitus/DalamudPlugins
18 | DOTNET_CLI_TELEMETRY_OPTOUT: true
19 |
20 | jobs:
21 | build:
22 | runs-on: windows-2022
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@v2
26 | with:
27 | submodules: recursive
28 | - name: Setup MSBuild
29 | uses: microsoft/setup-msbuild@v1.0.2
30 | - name: Download Dalamud
31 | run: |
32 | Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip
33 | Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev\"
34 | - name: Build
35 | run: |
36 | dotnet restore -r win ${{ env.SOLUTION_NAME }}.sln
37 | dotnet build --configuration Release
38 | - name: Test
39 | run: |
40 | dotnet test --no-restore --verbosity normal
41 | - uses: actions/upload-artifact@v2
42 | with:
43 | name: PluginRepoZip
44 | path: ${{ env.RELEASE_DIR }}
45 | if-no-files-found: error
46 |
47 | deploy:
48 | needs: build
49 | if: "contains(toJSON(github.event.commits.*.message), '[PUSH]')"
50 | runs-on: ubuntu-latest
51 | steps:
52 | - uses: actions/checkout@v2
53 | with:
54 | repository: ${{ env.PERSONAL_PLUGIN_REPO }}
55 | token: ${{ secrets.PAT }}
56 | - uses: actions/download-artifact@v2
57 | with:
58 | name: PluginRepoZip
59 | path: plugins/${{ env.INTERNAL_NAME }}
60 | - uses: EndBug/add-and-commit@v7
61 | with:
62 | add: --all
63 | author_name: GitHub Action
64 | author_email: github-actions[bot]@users.noreply.github.com
65 | message: Update ${{ env.INTERNAL_NAME }}
66 |
--------------------------------------------------------------------------------
/XIVComboExpanded/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | // General
9 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Preventing long lines", Scope = "module")]
10 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "I like regions", Scope = "module")]
11 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1123:Do not place regions within elements", Justification = "I like regions in elements too", Scope = "module")]
12 | [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "This is annoying", Scope = "module")]
13 | [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:Single-line comments should not be followed by blank line", Justification = "I like this better", Scope = "module")]
14 | [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "module")]
15 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "module")]
16 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
17 |
18 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Self explanatory.", Scope = "type", Target = "~T:XIVComboExpandedPlugin.CustomComboPreset")]
19 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Self explanatory.", Scope = "namespaceanddescendants", Target = "~N:XIVComboExpandedPlugin.Combos")]
20 | [assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group jobs together.", Scope = "namespaceanddescendants", Target = "~N:XIVComboExpandedPlugin.Combos")]
21 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1132:Do not combine fields", Justification = "Not necessary.", Scope = "namespaceanddescendants", Target = "~N:XIVComboExpandedPlugin.Combos")]
22 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/ADV.cs:
--------------------------------------------------------------------------------
1 | namespace XIVComboExpandedPlugin.Combos;
2 |
3 | internal static class ADV
4 | {
5 | public const byte ClassID = 0;
6 | public const byte JobID = 0;
7 |
8 | public const uint
9 | LucidDreaming = 1204,
10 | Swiftcast = 7561,
11 | AngelWhisper = 18317,
12 | VariantRaise2 = 29734;
13 |
14 | public static class Buffs
15 | {
16 | public const ushort
17 | Medicated = 49;
18 | }
19 |
20 | public static class Debuffs
21 | {
22 | public const ushort
23 | Placeholder = 0;
24 | }
25 |
26 | public static class Levels
27 | {
28 | public const byte
29 | Swiftcast = 18,
30 | VariantRaise2 = 90;
31 | }
32 | }
33 |
34 | internal class SwiftRaiseFeature : CustomCombo
35 | {
36 | protected internal override CustomComboPreset Preset => CustomComboPreset.AdvSwiftcastFeature;
37 |
38 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
39 | {
40 | if ((actionID == AST.Ascend && level >= AST.Levels.Ascend) ||
41 | (actionID == SCH.Resurrection && level >= SCH.Levels.Resurrection) ||
42 | (actionID == SGE.Egeiro && level >= SGE.Levels.Egeiro) ||
43 | (actionID == WHM.Raise && level >= WHM.Levels.Raise) ||
44 | (actionID == RDM.Verraise && level >= RDM.Levels.Verraise && !HasEffect(RDM.Buffs.Dualcast)) ||
45 | (actionID == BLU.AngelWhisper && level >= BLU.Levels.AngelWhisper))
46 | {
47 | if (level >= ADV.Levels.Swiftcast && IsOffCooldown(ADV.Swiftcast))
48 | return ADV.Swiftcast;
49 | }
50 |
51 | return actionID;
52 | }
53 | }
54 |
55 | internal class VariantRaiseFeature : CustomCombo
56 | {
57 | protected internal override CustomComboPreset Preset => CustomComboPreset.AdvVariantRaiseFeature;
58 |
59 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
60 | {
61 | if ((actionID == AST.Ascend && level >= AST.Levels.Ascend) ||
62 | (actionID == SCH.Resurrection && level >= SCH.Levels.Resurrection) ||
63 | (actionID == SGE.Egeiro && level >= SGE.Levels.Egeiro) ||
64 | (actionID == WHM.Raise && level >= WHM.Levels.Raise) ||
65 | (actionID == RDM.Verraise && level >= RDM.Levels.Verraise && !HasEffect(RDM.Buffs.Dualcast)) ||
66 | (actionID == BLU.AngelWhisper && level >= BLU.Levels.AngelWhisper))
67 | {
68 | // Per Splatoon:
69 | // 1069: solo
70 | // 1075: group
71 | // 1076: savage
72 | if (level >= ADV.Levels.VariantRaise2 && CurrentTerritory == 1075u)
73 | return ADV.VariantRaise2;
74 | }
75 |
76 | return actionID;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Attributes/CustomComboInfoAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | using XIVComboExpandedPlugin.Combos;
5 |
6 | namespace XIVComboExpandedPlugin.Attributes;
7 |
8 | ///
9 | /// Attribute documenting additional information for each combo.
10 | ///
11 | [AttributeUsage(AttributeTargets.Field)]
12 | internal class CustomComboInfoAttribute : Attribute
13 | {
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// Display name.
18 | /// Combo description.
19 | /// Associated job ID.
20 | /// Display order.
21 | internal CustomComboInfoAttribute(string fancyName, string description, byte jobID, [CallerLineNumber] int order = 0)
22 | {
23 | this.FancyName = fancyName;
24 | this.Description = description;
25 | this.JobID = jobID;
26 | this.Order = order;
27 | }
28 |
29 | ///
30 | /// Gets the display name.
31 | ///
32 | public string FancyName { get; }
33 |
34 | ///
35 | /// Gets the description.
36 | ///
37 | public string Description { get; }
38 |
39 | ///
40 | /// Gets the job ID.
41 | ///
42 | public byte JobID { get; }
43 |
44 | ///
45 | /// Gets the display order.
46 | ///
47 | public int Order { get; }
48 |
49 | ///
50 | /// Gets the job name.
51 | ///
52 | public string JobName => JobIDToName(this.JobID);
53 |
54 | private static string JobIDToName(byte key)
55 | {
56 | return key switch
57 | {
58 | 0 => "Adventurer",
59 | 1 => "Gladiator",
60 | 2 => "Pugilist",
61 | 3 => "Marauder",
62 | 4 => "Lancer",
63 | 5 => "Archer",
64 | 6 => "Conjurer",
65 | 7 => "Thaumaturge",
66 | 8 => "Carpenter",
67 | 9 => "Blacksmith",
68 | 10 => "Armorer",
69 | 11 => "Goldsmith",
70 | 12 => "Leatherworker",
71 | 13 => "Weaver",
72 | 14 => "Alchemist",
73 | 15 => "Culinarian",
74 | 16 => "Miner",
75 | 17 => "Botanist",
76 | 18 => "Fisher",
77 | 19 => "Paladin",
78 | 20 => "Monk",
79 | 21 => "Warrior",
80 | 22 => "Dragoon",
81 | 23 => "Bard",
82 | 24 => "White Mage",
83 | 25 => "Black Mage",
84 | 26 => "Arcanist",
85 | 27 => "Summoner",
86 | 28 => "Scholar",
87 | 29 => "Rogue",
88 | 30 => "Ninja",
89 | 31 => "Machinist",
90 | 32 => "Dark Knight",
91 | 33 => "Astrologian",
92 | 34 => "Samurai",
93 | 35 => "Red Mage",
94 | 36 => "Blue Mage",
95 | 37 => "Gunbreaker",
96 | 38 => "Dancer",
97 | 39 => "Reaper",
98 | 40 => "Sage",
99 | DOH.JobID => "Disciples of the Hand",
100 | DOL.JobID => "Disciples of the Land",
101 | _ => "Unknown",
102 | };
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/XIVComboExpanded/XIVComboExpanded.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | attick, daemitus
5 | -
6 | 1.4.0.24
7 | This plugin condenses combos and mutually exclusive abilities onto a single button.
8 | Copyleft attick 2020 baybeeee
9 | https://github.com/daemitus/XIVComboPlugin
10 |
11 |
12 |
13 | net7.0-windows
14 | x64
15 | enable
16 | latest
17 | true
18 | false
19 | true
20 | false
21 | bin\$(Configuration)\
22 | CS1591
23 |
24 |
25 |
26 |
27 | true
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | $(appdata)\XIVLauncher\addon\Hooks\dev\
36 |
37 |
38 |
39 |
40 | $(AssemblySearchPaths);
41 | $(DalamudLibPath);
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | all
54 | runtime; build; native; contentfiles; analyzers; buildtransitive
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Service.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Data;
2 | using Dalamud.Game;
3 | using Dalamud.Game.ClientState;
4 | using Dalamud.Game.ClientState.Buddy;
5 | using Dalamud.Game.ClientState.Conditions;
6 | using Dalamud.Game.ClientState.JobGauge;
7 | using Dalamud.Game.ClientState.Objects;
8 | using Dalamud.Game.Command;
9 | using Dalamud.Game.Gui;
10 | using Dalamud.IoC;
11 | using Dalamud.Plugin;
12 | using Dalamud.Plugin.Services;
13 |
14 | namespace XIVComboExpandedPlugin;
15 |
16 | ///
17 | /// Dalamud and plugin services.
18 | ///
19 | internal class Service
20 | {
21 | ///
22 | /// Gets or sets the plugin configuration.
23 | ///
24 | internal static PluginConfiguration Configuration { get; set; } = null!;
25 |
26 | ///
27 | /// Gets or sets the plugin caching mechanism.
28 | ///
29 | internal static CustomComboCache ComboCache { get; set; } = null!;
30 |
31 | ///
32 | /// Gets or sets the plugin icon replacer.
33 | ///
34 | internal static IconReplacer IconReplacer { get; set; } = null!;
35 |
36 | ///
37 | /// Gets or sets the plugin address resolver.
38 | ///
39 | internal static PluginAddressResolver Address { get; set; } = null!;
40 |
41 | ///
42 | /// Gets the Dalamud plugin interface.
43 | ///
44 | [PluginService]
45 | internal static DalamudPluginInterface Interface { get; private set; } = null!;
46 |
47 | ///
48 | /// Gets the Dalamud buddy list.
49 | ///
50 | [PluginService]
51 | internal static IBuddyList BuddyList { get; private set; } = null!;
52 |
53 | ///
54 | /// Gets the Dalamud chat gui.
55 | ///
56 | [PluginService]
57 | internal static IChatGui ChatGui { get; private set; } = null!;
58 |
59 | ///
60 | /// Gets the Dalamud client state.
61 | ///
62 | [PluginService]
63 | internal static IClientState ClientState { get; private set; } = null!;
64 |
65 | ///
66 | /// Gets the Dalamud command manager.
67 | ///
68 | [PluginService]
69 | internal static ICommandManager CommandManager { get; private set; } = null!;
70 |
71 | ///
72 | /// Gets the Dalamud condition.
73 | ///
74 | [PluginService]
75 | internal static ICondition Condition { get; private set; } = null!;
76 |
77 | ///
78 | /// Gets the Dalamud data manager.
79 | ///
80 | [PluginService]
81 | internal static IDataManager DataManager { get; private set; } = null!;
82 |
83 | ///
84 | /// Gets the Dalamud framework manager.
85 | ///
86 | [PluginService]
87 | internal static IFramework Framework { get; private set; } = null!;
88 |
89 | ///
90 | /// Gets the Dalamud job gauges.
91 | ///
92 | [PluginService]
93 | internal static IJobGauges JobGauges { get; private set; } = null!;
94 |
95 | ///
96 | /// Gets the Dalamud object table.
97 | ///
98 | [PluginService]
99 | internal static IObjectTable ObjectTable { get; private set; } = null!;
100 |
101 | ///
102 | /// Gets the Dalamud target manager.
103 | ///
104 | [PluginService]
105 | internal static ITargetManager TargetManager { get; private set; } = null!;
106 | }
107 |
--------------------------------------------------------------------------------
/XIVComboExpanded/IconReplacer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | using Dalamud.Hooking;
7 | using Dalamud.Logging;
8 | using Dalamud.Plugin.Services;
9 | using XIVComboExpandedPlugin.Combos;
10 |
11 | namespace XIVComboExpandedPlugin;
12 |
13 | ///
14 | /// This class facilitates the icon replacing.
15 | ///
16 | internal sealed partial class IconReplacer : IDisposable
17 | {
18 | private readonly List customCombos;
19 | private readonly Hook isIconReplaceableHook;
20 | private readonly Hook getIconHook;
21 |
22 | private IntPtr actionManager = IntPtr.Zero;
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | public IconReplacer(IGameInteropProvider gameInteropProvider)
28 | {
29 | this.customCombos = Assembly.GetAssembly(typeof(CustomCombo))!.GetTypes()
30 | .Where(t => !t.IsAbstract && IsDescendant(t, typeof(CustomCombo)))
31 | .Select(t => Activator.CreateInstance(t))
32 | .Cast()
33 | .ToList();
34 |
35 | this.getIconHook = gameInteropProvider.HookFromAddress(Service.Address.GetAdjustedActionId, this.GetIconDetour);
36 | this.isIconReplaceableHook = gameInteropProvider.HookFromAddress(Service.Address.IsActionIdReplaceable, this.IsIconReplaceableDetour);
37 |
38 | this.getIconHook.Enable();
39 | this.isIconReplaceableHook.Enable();
40 | }
41 |
42 | private static bool IsDescendant(Type clazz, Type ancestor)
43 | {
44 | if (clazz.BaseType == null) return false;
45 | if (clazz.BaseType == ancestor) return true;
46 | return IsDescendant(clazz.BaseType, ancestor);
47 | }
48 |
49 | private delegate ulong IsIconReplaceableDelegate(uint actionID);
50 |
51 | private delegate uint GetIconDelegate(IntPtr actionManager, uint actionID);
52 |
53 | ///
54 | public void Dispose()
55 | {
56 | this.getIconHook?.Dispose();
57 | this.isIconReplaceableHook?.Dispose();
58 | }
59 |
60 | ///
61 | /// Calls the original hook.
62 | ///
63 | /// Action ID.
64 | /// The result from the hook.
65 | internal uint OriginalHook(uint actionID)
66 | => this.getIconHook.Original(this.actionManager, actionID);
67 |
68 | private unsafe uint GetIconDetour(IntPtr actionManager, uint actionID)
69 | {
70 | this.actionManager = actionManager;
71 |
72 | try
73 | {
74 | if (Service.ClientState.LocalPlayer == null)
75 | return this.OriginalHook(actionID);
76 |
77 | var lastComboMove = *(uint*)Service.Address.LastComboMove;
78 | var comboTime = *(float*)Service.Address.ComboTimer;
79 | var level = Service.ClientState.LocalPlayer?.Level ?? 0;
80 |
81 | foreach (var combo in this.customCombos)
82 | {
83 | if (combo.TryInvoke(actionID, level, lastComboMove, comboTime, out var newActionID))
84 | return newActionID;
85 | }
86 |
87 | return this.OriginalHook(actionID);
88 | }
89 | catch (Exception ex)
90 | {
91 | PluginLog.Error(ex, "Don't crash the game");
92 | return this.OriginalHook(actionID);
93 | }
94 | }
95 |
96 | private ulong IsIconReplaceableDetour(uint actionID) => 1;
97 | }
98 |
--------------------------------------------------------------------------------
/XIVComboExpanded/CooldownData.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace XIVComboExpandedPlugin;
4 |
5 | ///
6 | /// Internal cooldown data.
7 | ///
8 | [StructLayout(LayoutKind.Explicit)]
9 | internal struct CooldownData
10 | {
11 | [FieldOffset(0x0)]
12 | private readonly bool isCooldown;
13 |
14 | [FieldOffset(0x4)]
15 | private readonly uint actionID;
16 |
17 | [FieldOffset(0x8)]
18 | private readonly float cooldownElapsed;
19 |
20 | [FieldOffset(0xC)]
21 | private readonly float cooldownTotal;
22 |
23 | ///
24 | /// Gets a value indicating whether the action is on cooldown.
25 | ///
26 | public bool IsCooldown
27 | {
28 | get
29 | {
30 | var (cur, max) = Service.ComboCache.GetMaxCharges(this.ActionID);
31 | if (cur == max)
32 | return this.isCooldown;
33 |
34 | return this.cooldownElapsed < this.CooldownTotal;
35 | }
36 | }
37 |
38 | ///
39 | /// Gets the action ID on cooldown.
40 | ///
41 | public uint ActionID => this.actionID;
42 |
43 | ///
44 | /// Gets the elapsed cooldown time.
45 | ///
46 | public float CooldownElapsed
47 | {
48 | get
49 | {
50 | if (this.cooldownElapsed == 0)
51 | return 0;
52 |
53 | if (this.cooldownElapsed > this.CooldownTotal)
54 | return 0;
55 |
56 | return this.cooldownElapsed;
57 | }
58 | }
59 |
60 | ///
61 | /// Gets the total cooldown time.
62 | ///
63 | public float CooldownTotal
64 | {
65 | get
66 | {
67 | if (this.cooldownTotal == 0)
68 | return 0;
69 |
70 | var (cur, max) = Service.ComboCache.GetMaxCharges(this.ActionID);
71 | if (cur == max)
72 | return this.cooldownTotal;
73 |
74 | // Rebase to the current charge count
75 | var total = this.cooldownTotal / max * cur;
76 |
77 | if (this.cooldownElapsed > total)
78 | return 0;
79 |
80 | return total;
81 | }
82 | }
83 |
84 | ///
85 | /// Gets the cooldown time remaining.
86 | ///
87 | public float CooldownRemaining => this.IsCooldown ? this.CooldownTotal - this.CooldownElapsed : 0;
88 |
89 | ///
90 | /// Gets the maximum number of charges for an action at the current level.
91 | ///
92 | /// Number of charges.
93 | public ushort MaxCharges => Service.ComboCache.GetMaxCharges(this.ActionID).Current;
94 |
95 | ///
96 | /// Gets a value indicating whether the action has charges, not charges available.
97 | ///
98 | public bool HasCharges => this.MaxCharges > 1;
99 |
100 | ///
101 | /// Gets the remaining number of charges for an action.
102 | ///
103 | public ushort RemainingCharges
104 | {
105 | get
106 | {
107 | var (cur, _) = Service.ComboCache.GetMaxCharges(this.ActionID);
108 |
109 | if (!this.IsCooldown)
110 | return cur;
111 |
112 | return (ushort)(this.CooldownElapsed / (this.CooldownTotal / this.MaxCharges));
113 | }
114 | }
115 |
116 | ///
117 | /// Gets the cooldown time remaining until the next charge.
118 | ///
119 | public float ChargeCooldownRemaining
120 | {
121 | get
122 | {
123 | if (!this.IsCooldown)
124 | return 0;
125 |
126 | var (cur, _) = Service.ComboCache.GetMaxCharges(this.ActionID);
127 |
128 | return this.CooldownRemaining % (this.CooldownTotal / cur);
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/DOL.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.Conditions;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class DOL
6 | {
7 | public const byte ClassID = 0;
8 | public const byte JobID = 51;
9 |
10 | public const uint
11 | AgelessWords = 215,
12 | SolidReason = 232,
13 | Cast = 289,
14 | Hook = 296,
15 | CastLight = 2135,
16 | Snagging = 4100,
17 | SurfaceSlap = 4595,
18 | Gig = 7632,
19 | VeteranTrade = 7906,
20 | NaturesBounty = 7909,
21 | Salvage = 7910,
22 | MinWiseToTheWorld = 26521,
23 | BtnWiseToTheWorld = 26522,
24 | ElectricCurrent = 26872,
25 | PrizeCatch = 26806;
26 |
27 | public static class Buffs
28 | {
29 | public const ushort
30 | EurekaMoment = 2765;
31 | }
32 |
33 | public static class Debuffs
34 | {
35 | public const ushort
36 | Placeholder = 0;
37 | }
38 |
39 | public static class Levels
40 | {
41 | public const byte
42 | Cast = 1,
43 | Hook = 1,
44 | Snagging = 36,
45 | Gig = 61,
46 | Salvage = 67,
47 | VeteranTrade = 63,
48 | NaturesBounty = 69,
49 | SurfaceSlap = 71,
50 | PrizeCatch = 81,
51 | WiseToTheWorld = 90;
52 | }
53 | }
54 |
55 | internal class MinerEurekaFeature : CustomCombo
56 | {
57 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DolEurekaFeature;
58 |
59 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
60 | {
61 | if (actionID == DOL.SolidReason)
62 | {
63 | if (level >= DOL.Levels.WiseToTheWorld && HasEffect(DOL.Buffs.EurekaMoment))
64 | return DOL.MinWiseToTheWorld;
65 | }
66 |
67 | if (actionID == DOL.AgelessWords)
68 | {
69 | if (level >= DOL.Levels.WiseToTheWorld && HasEffect(DOL.Buffs.EurekaMoment))
70 | return DOL.BtnWiseToTheWorld;
71 | }
72 |
73 | return actionID;
74 | }
75 | }
76 |
77 | internal class FisherCast : CustomCombo
78 | {
79 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DolAny;
80 |
81 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
82 | {
83 | if (actionID == DOL.Cast)
84 | {
85 | if (IsEnabled(CustomComboPreset.DolCastHookFeature))
86 | {
87 | if (HasCondition(ConditionFlag.Fishing))
88 | return DOL.Hook;
89 | }
90 |
91 | if (IsEnabled(CustomComboPreset.DolCastGigFeature))
92 | {
93 | if (HasCondition(ConditionFlag.Diving))
94 | return DOL.Gig;
95 | }
96 | }
97 |
98 | if (actionID == DOL.SurfaceSlap)
99 | {
100 | if (IsEnabled(CustomComboPreset.DolSurfaceTradeFeature))
101 | {
102 | if (HasCondition(ConditionFlag.Diving))
103 | return DOL.VeteranTrade;
104 | }
105 | }
106 |
107 | if (actionID == DOL.PrizeCatch)
108 | {
109 | if (IsEnabled(CustomComboPreset.DolPrizeBountyFeature))
110 | {
111 | if (HasCondition(ConditionFlag.Diving))
112 | return DOL.NaturesBounty;
113 | }
114 | }
115 |
116 | if (actionID == DOL.Snagging)
117 | {
118 | if (IsEnabled(CustomComboPreset.DolSnaggingSalvageFeature))
119 | {
120 | if (HasCondition(ConditionFlag.Diving))
121 | return DOL.Salvage;
122 | }
123 | }
124 |
125 | if (actionID == DOL.CastLight)
126 | {
127 | if (IsEnabled(CustomComboPreset.DolCastLightElectricCurrentFeature))
128 | {
129 | if (HasCondition(ConditionFlag.Diving))
130 | return DOL.ElectricCurrent;
131 | }
132 | }
133 |
134 | return actionID;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/XIVComboExpanded/PluginConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using Dalamud.Configuration;
6 | using Dalamud.Utility;
7 | using Newtonsoft.Json;
8 | using XIVComboExpandedPlugin.Attributes;
9 | using XIVComboExpandedPlugin.Combos;
10 |
11 | namespace XIVComboExpandedPlugin;
12 |
13 | ///
14 | /// Plugin configuration.
15 | ///
16 | [Serializable]
17 | public class PluginConfiguration : IPluginConfiguration
18 | {
19 | private static readonly HashSet SecretCombos;
20 | private static readonly Dictionary ConflictingCombos;
21 | private static readonly Dictionary ParentCombos; // child: parent
22 |
23 | static PluginConfiguration()
24 | {
25 | SecretCombos = Enum.GetValues()
26 | .Where(preset => preset.GetAttribute() != default)
27 | .ToHashSet();
28 |
29 | ConflictingCombos = Enum.GetValues()
30 | .Distinct() // Prevent ArgumentExceptions from adding the same int twice, should not be seen anymore
31 | .ToDictionary(
32 | preset => preset,
33 | preset => preset.GetAttribute()?.ConflictingPresets ?? Array.Empty());
34 |
35 | ParentCombos = Enum.GetValues()
36 | .Distinct() // Prevent ArgumentExceptions from adding the same int twice, should not be seen anymore
37 | .ToDictionary(
38 | preset => preset,
39 | preset => preset.GetAttribute()?.ParentPreset);
40 | }
41 |
42 | ///
43 | /// Gets or sets the configuration version.
44 | ///
45 | public int Version { get; set; } = 5;
46 |
47 | ///
48 | /// Gets or sets the collection of enabled combos.
49 | ///
50 | [JsonProperty("EnabledActionsV5")]
51 | public HashSet EnabledActions { get; set; } = new();
52 |
53 | ///
54 | /// Gets or sets the collection of enabled combos.
55 | ///
56 | [JsonProperty("EnabledActionsV4")]
57 | public HashSet EnabledActions4 { get; set; } = new();
58 |
59 | ///
60 | /// Gets or sets a value indicating whether to allow and display secret combos.
61 | ///
62 | [JsonProperty("Debug")]
63 | public bool EnableSecretCombos { get; set; } = false;
64 |
65 | ///
66 | /// Gets or sets a value indicating whether to hide the children of a feature if it is disabled.
67 | ///
68 | public bool HideChildren { get; set; } = false;
69 |
70 | ///
71 | /// Gets or sets an array of 4 ability IDs to interact with the combo.
72 | ///
73 | public uint[] DancerDanceCompatActionIDs { get; set; } = new uint[]
74 | {
75 | DNC.Cascade,
76 | DNC.Flourish,
77 | DNC.FanDance1,
78 | DNC.FanDance2,
79 | };
80 |
81 | ///
82 | /// Save the configuration to disk.
83 | ///
84 | public void Save()
85 | => Service.Interface.SavePluginConfig(this);
86 |
87 | ///
88 | /// Gets a value indicating whether a preset is enabled.
89 | ///
90 | /// Preset to check.
91 | /// The boolean representation.
92 | public bool IsEnabled(CustomComboPreset preset)
93 | => this.EnabledActions.Contains(preset) && (this.EnableSecretCombos || !this.IsSecret(preset));
94 |
95 | ///
96 | /// Gets a value indicating whether a preset is secret.
97 | ///
98 | /// Preset to check.
99 | /// The boolean representation.
100 | public bool IsSecret(CustomComboPreset preset)
101 | => SecretCombos.Contains(preset);
102 |
103 | ///
104 | /// Gets an array of conflicting combo presets.
105 | ///
106 | /// Preset to check.
107 | /// The conflicting presets.
108 | public CustomComboPreset[] GetConflicts(CustomComboPreset preset)
109 | => ConflictingCombos[preset];
110 |
111 | ///
112 | /// Gets the parent combo preset if it exists, or null.
113 | ///
114 | /// Preset to check.
115 | /// The parent preset.
116 | public CustomComboPreset? GetParent(CustomComboPreset preset)
117 | => ParentCombos[preset];
118 | }
119 |
--------------------------------------------------------------------------------
/statement.txt:
--------------------------------------------------------------------------------
1 | Hello! I'm sure this plugin is bound to raise a few eyebrows in the community, so I'd like to give my stance and perspective on concerns publicly. I've been playing FFXIV without a sub lapse since around patch 2.5. It's a wonderful game that I love playing, and I have no desire to worsen its quality or see it fail or any other number of bad things.
2 |
3 | But just because I love this game doesn't mean I have to be content with its current state. As we've seen recently with the Paisely Park incident, there are definitely some lines that SE, the XIV team, and Yoshi-p consider to be "too far", and work to foil any projects that cross that line. In Live Letter #57, Yoshi-p has a public monologue about such tools, and how they have no desire to go full tryhard to ensure no bad third-party programs are being used. In that discussion, he mentions how some mods improve the game, gain a lot of popularity, and eventually get added in as official features, a la Counter-Strike becoming an entire standalone game. We can see a similar reaction to Paisely Park: It became hugely popular with the community, so the devs took that feedback and implemented something similar. It's not a perfect comparison, but it's pretty close.
4 |
5 | My aim is only ever to implement such features as fit that bill: Something that does not have a substantial impact on the way the game plays, but does bring about a more enjoyable experience. You can think of them as "cheating quality of life" if you'd like. I have no desire to "cheapen" the experience of playing this game, and only want it to be more enjoyable by improving or tweaking features that don't seem perfect.
6 |
7 | So let's talk about XIVCombo. At its core, it largely implements the functionality of buttons that exist in PvP. There, all weaponskill combos are consolidated onto a single button. Additionally, some other buttons that are mutually exclusive get put onto one button. Let's look at a few examples and how they work in PvE content:
8 |
9 | - Jump and Mirage Dive: As a DRG, you cannot use Mirage Dive unless you gain the Dive Ready buff from using (High) Jump. This buff is notably shorter in duration than Jump's cooldown. Therefore, if you for some reason decide to let the buff expire instead of using Mirage Dive, you STILL aren't able to use Jump. In other words, the ability to press one of those buttons automatically means the other can't be pressed. Further, because they are intrinsically linked, it makes sense to put them onto one button. There are numerous abilities spread across multiple jobs that are like this too. Hypercharge/Heat Blast, Third Eye/Seigan, Dream Within a Dream/Assassinate, and more. All of these can be consolidated.
10 |
11 | - Draw and Play: I don't know why they changed this. Draw was FINE the way it was before Shadowbringers. The addition of the Play ability is pure button bloat. All I'm doing is reverting to the behavior of previous game versions.
12 |
13 | - Transpose and Umbral Soul: This one is a bit interesting, because it has extremely minor implications of a "forced" playstyle. However, one thing about BLM I think should be intuitively obvious by the time you hit level 76: You should never use Transpose to enter Astral Fire except as an absolute last option. Therefore, the use of Umbral Soul when in Umbral Ice is superior to using Transpose in every situation. So it makes sense to not even have the option to use Transpose when Umbral Soul is available to use.
14 |
15 | For some jobs, I am intentionally not implementing features because I feel like they would trivialize playing the job by a large amount. This is why MNK, DNC, and RDM are largely untouched. I do not want to turn DNC or RDM into one-button jobs, and with Perfect Balance, MNK would require too much "smart" thinking from the program in order to work properly.
16 |
17 | My end goal in all of this is to encourage the XIV team to open their design options. As it stands, each expac adds and removes abilities for each job. It seems like the design team has an "optimal" number of keybinds they want a job to have, and make sure they are never too far above or below that number. A problem that I observe is that the number of redundant/uninteresting buttons takes up too many of that "magic number" of binds that the team aims to have. Let's look at DRG as an extreme example:
18 | - Chaos Thrust and Full Thrust combos both free up 3 buttons.
19 | - BOTD and Stardiver frees up 1 button.
20 | - Jump and Mirage Dive frees up 1 button.
21 | In total, by consolidating weaponskills and mutually exclusive buttons, DRG gains EIGHT new free spaces for new abilities. What sort of new job mechanics, buffs, or other abilities could the development team add in those eight slots? DRG gains the most free spaces of any jobs, but the possibilities are exciting and interesting. For a job that has a very linear use of abilities, there is little difference in satisfaction between pressing 1-2-3-4 and 1-1-1-1.
22 |
23 | Similarly, I think tanks could benefit massively from having more free slots. While each tank has their own distinctive "flavor" cooldown (TBN, Nascent, Heart of Stone, Sheltron), the overall tanking plan is extremely similar for all tanks. With more spots for abilities freed up, tanks could gain more disctinctive abilities. Mitigation and survival could become an even more dynamic experience. But the more redundant buttons there are, the fewer abilities the developers can play around with.
24 |
25 | I hope I've managed to convince you that I do not want to see this game in ruins. I want it to flourish and become the best it can possibly be. My hope is that this plugin serves as a community voice, giving feedback that would normally never reach the developers' ears.
--------------------------------------------------------------------------------
/XIVComboExpanded/CustomComboCache.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using Dalamud.Game;
5 | using Dalamud.Game.ClientState.JobGauge.Types;
6 | using Dalamud.Game.ClientState.Objects.Types;
7 | using Dalamud.Game.ClientState.Statuses;
8 | using Dalamud.Plugin.Services;
9 |
10 | namespace XIVComboExpandedPlugin;
11 |
12 | ///
13 | /// Cached conditional combo logic.
14 | ///
15 | internal partial class CustomComboCache : IDisposable
16 | {
17 | private const uint InvalidObjectID = 0xE000_0000;
18 |
19 | // Invalidate these
20 | private readonly Dictionary<(uint StatusID, uint? TargetID, uint? SourceID), Status?> statusCache = new();
21 | private readonly Dictionary cooldownCache = new();
22 |
23 | // Do not invalidate these
24 | private readonly Dictionary cooldownGroupCache = new();
25 | private readonly Dictionary jobGaugeCache = new();
26 | private readonly Dictionary<(uint ActionID, uint ClassJobID, byte Level), (ushort CurrentMax, ushort Max)> chargesCache = new();
27 |
28 | ///
29 | /// Initializes a new instance of the class.
30 | ///
31 | public CustomComboCache()
32 | {
33 | Service.Framework.Update += this.Framework_Update;
34 | }
35 |
36 | private delegate IntPtr GetActionCooldownSlotDelegate(IntPtr actionManager, int cooldownGroup);
37 |
38 | ///
39 | public void Dispose()
40 | {
41 | Service.Framework.Update -= this.Framework_Update;
42 | }
43 |
44 | ///
45 | /// Get a job gauge.
46 | ///
47 | /// Type of job gauge.
48 | /// The job gauge.
49 | internal T GetJobGauge() where T : JobGaugeBase
50 | {
51 | if (!this.jobGaugeCache.TryGetValue(typeof(T), out var gauge))
52 | gauge = this.jobGaugeCache[typeof(T)] = Service.JobGauges.Get();
53 |
54 | return (T)gauge;
55 | }
56 |
57 | ///
58 | /// Finds a status on the given object.
59 | ///
60 | /// Status effect ID.
61 | /// Object to look for effects on.
62 | /// Source object ID.
63 | /// Status object or null.
64 | internal Status? GetStatus(uint statusID, GameObject? obj, uint? sourceID)
65 | {
66 | var key = (statusID, obj?.ObjectId, sourceID);
67 | if (this.statusCache.TryGetValue(key, out var found))
68 | return found;
69 |
70 | if (obj is null)
71 | return this.statusCache[key] = null;
72 |
73 | if (obj is not BattleChara chara)
74 | return this.statusCache[key] = null;
75 |
76 | foreach (var status in chara.StatusList)
77 | {
78 | if (status.StatusId == statusID && (!sourceID.HasValue || status.SourceId == 0 || status.SourceId == InvalidObjectID || status.SourceId == sourceID))
79 | return this.statusCache[key] = status;
80 | }
81 |
82 | return this.statusCache[key] = null;
83 | }
84 |
85 | ///
86 | /// Gets the cooldown data for an action.
87 | ///
88 | /// Action ID to check.
89 | /// Cooldown data.
90 | internal unsafe CooldownData GetCooldown(uint actionID)
91 | {
92 | if (this.cooldownCache.TryGetValue(actionID, out var found))
93 | return found;
94 |
95 | var actionManager = FFXIVClientStructs.FFXIV.Client.Game.ActionManager.Instance();
96 | if (actionManager == null)
97 | return this.cooldownCache[actionID] = default;
98 |
99 | var cooldownGroup = this.GetCooldownGroup(actionID);
100 |
101 | var cooldownPtr = actionManager->GetRecastGroupDetail(cooldownGroup - 1);
102 | cooldownPtr->ActionID = actionID;
103 |
104 | return this.cooldownCache[actionID] = *(CooldownData*)cooldownPtr;
105 | }
106 |
107 | ///
108 | /// Get the maximum number of charges for an action.
109 | ///
110 | /// Action ID to check.
111 | /// Max number of charges at current and max level.
112 | internal unsafe (ushort Current, ushort Max) GetMaxCharges(uint actionID)
113 | {
114 | var player = Service.ClientState.LocalPlayer;
115 | if (player == null)
116 | return (0, 0);
117 |
118 | var job = player.ClassJob.Id;
119 | var level = player.Level;
120 | if (job == 0 || level == 0)
121 | return (0, 0);
122 |
123 | var key = (actionID, job, level);
124 | if (this.chargesCache.TryGetValue(key, out var found))
125 | return found;
126 |
127 | var cur = FFXIVClientStructs.FFXIV.Client.Game.ActionManager.GetMaxCharges(actionID, 0);
128 | var max = FFXIVClientStructs.FFXIV.Client.Game.ActionManager.GetMaxCharges(actionID, 90);
129 | return this.chargesCache[key] = (cur, max);
130 | }
131 |
132 | private byte GetCooldownGroup(uint actionID)
133 | {
134 | if (this.cooldownGroupCache.TryGetValue(actionID, out var cooldownGroup))
135 | return cooldownGroup;
136 |
137 | var sheet = Service.DataManager.GetExcelSheet()!;
138 | var row = sheet.GetRow(actionID);
139 |
140 | return this.cooldownGroupCache[actionID] = row!.CooldownGroup;
141 | }
142 |
143 | private unsafe void Framework_Update(IFramework framework)
144 | {
145 | this.statusCache.Clear();
146 | this.cooldownCache.Clear();
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/WHM.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class WHM
6 | {
7 | public const byte ClassID = 6;
8 | public const byte JobID = 24;
9 |
10 | public const uint
11 | Cure = 120,
12 | Medica = 124,
13 | Raise = 125,
14 | Cure2 = 135,
15 | PresenceOfMind = 136,
16 | Holy = 139,
17 | Benediction = 140,
18 | Asylum = 3569,
19 | Tetragrammaton = 3570,
20 | Assize = 3571,
21 | PlenaryIndulgence = 7433,
22 | AfflatusSolace = 16531,
23 | AfflatusRapture = 16534,
24 | AfflatusMisery = 16535,
25 | Temperance = 16536,
26 | Holy3 = 25860,
27 | Aquaveil = 25861,
28 | LiturgyOfTheBell = 25862;
29 |
30 | public static class Buffs
31 | {
32 | public const ushort
33 | Placeholder = 0;
34 | }
35 |
36 | public static class Debuffs
37 | {
38 | public const ushort
39 | Placeholder = 0;
40 | }
41 |
42 | public static class Levels
43 | {
44 | public const byte
45 | Raise = 12,
46 | Cure2 = 30,
47 | AfflatusSolace = 52,
48 | AfflatusMisery = 74,
49 | AfflatusRapture = 76;
50 | }
51 | }
52 |
53 | internal class WhiteMageAfflatusSolace : CustomCombo
54 | {
55 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WhiteMageSolaceMiseryFeature;
56 |
57 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
58 | {
59 | if (actionID == WHM.AfflatusSolace)
60 | {
61 | var gauge = GetJobGauge();
62 |
63 | if (level >= WHM.Levels.AfflatusMisery && gauge.BloodLily == 3)
64 | {
65 | if (IsEnabled(CustomComboPreset.WhiteMageSolaceMiseryTargetFeature))
66 | {
67 | if (TargetIsEnemy())
68 | return WHM.AfflatusMisery;
69 | }
70 | else
71 | {
72 | return WHM.AfflatusMisery;
73 | }
74 | }
75 | }
76 |
77 | return actionID;
78 | }
79 | }
80 |
81 | internal class WhiteMageAfflatusRapture : CustomCombo
82 | {
83 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WhiteMageRaptureMiseryFeature;
84 |
85 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
86 | {
87 | if (actionID == WHM.AfflatusRapture)
88 | {
89 | var gauge = GetJobGauge();
90 |
91 | if (level >= WHM.Levels.AfflatusMisery && gauge.BloodLily == 3 && TargetIsEnemy())
92 | return WHM.AfflatusMisery;
93 | }
94 |
95 | return actionID;
96 | }
97 | }
98 |
99 | internal class WhiteMageHoly : CustomCombo
100 | {
101 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WhiteMageHolyMiseryFeature;
102 |
103 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
104 | {
105 | if (actionID == WHM.Holy || actionID == WHM.Holy3)
106 | {
107 | var gauge = GetJobGauge();
108 |
109 | if (level >= WHM.Levels.AfflatusMisery && gauge.BloodLily == 3 && TargetIsEnemy())
110 | return WHM.AfflatusMisery;
111 | }
112 |
113 | return actionID;
114 | }
115 | }
116 |
117 | internal class WhiteMageCure2 : CustomCombo
118 | {
119 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WhmAny;
120 |
121 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
122 | {
123 | if (actionID == WHM.Cure2)
124 | {
125 | var gauge = GetJobGauge();
126 |
127 | if (IsEnabled(CustomComboPreset.WhiteMageCureFeature))
128 | {
129 | if (level < WHM.Levels.Cure2)
130 | return WHM.Cure;
131 | }
132 |
133 | if (IsEnabled(CustomComboPreset.WhiteMageAfflatusFeature))
134 | {
135 | if (IsEnabled(CustomComboPreset.WhiteMageSolaceMiseryFeature))
136 | {
137 | if (level >= WHM.Levels.AfflatusMisery && gauge.BloodLily == 3)
138 | {
139 | if (IsEnabled(CustomComboPreset.WhiteMageSolaceMiseryTargetFeature))
140 | {
141 | if (TargetIsEnemy())
142 | return WHM.AfflatusMisery;
143 | }
144 | else
145 | {
146 | return WHM.AfflatusMisery;
147 | }
148 | }
149 | }
150 |
151 | if (level >= WHM.Levels.AfflatusSolace && gauge.Lily > 0)
152 | return WHM.AfflatusSolace;
153 | }
154 | }
155 |
156 | return actionID;
157 | }
158 | }
159 |
160 | internal class WhiteMageMedica : CustomCombo
161 | {
162 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WhmAny;
163 |
164 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
165 | {
166 | if (actionID == WHM.Medica)
167 | {
168 | var gauge = GetJobGauge();
169 |
170 | if (IsEnabled(CustomComboPreset.WhiteMageAfflatusFeature))
171 | {
172 | if (IsEnabled(CustomComboPreset.WhiteMageRaptureMiseryFeature))
173 | {
174 | if (level >= WHM.Levels.AfflatusMisery && gauge.BloodLily == 3 && TargetIsEnemy())
175 | return WHM.AfflatusMisery;
176 | }
177 |
178 | if (level >= WHM.Levels.AfflatusRapture && gauge.Lily > 0)
179 | return WHM.AfflatusRapture;
180 | }
181 | }
182 |
183 | return actionID;
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/AST.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | using Dalamud.Game.ClientState.JobGauge.Enums;
4 | using Dalamud.Game.ClientState.JobGauge.Types;
5 |
6 | namespace XIVComboExpandedPlugin.Combos;
7 |
8 | internal static class AST
9 | {
10 | public const byte JobID = 33;
11 |
12 | public const uint
13 | Draw = 3590,
14 | Redraw = 3593,
15 | Benefic = 3594,
16 | Malefic = 3596,
17 | Malefic2 = 3598,
18 | Ascend = 3603,
19 | Lightspeed = 3606,
20 | Benefic2 = 3610,
21 | Synastry = 3612,
22 | CollectiveUnconscious = 3613,
23 | Gravity = 3615,
24 | Balance = 4401,
25 | Bole = 4404,
26 | Arrow = 4402,
27 | Spear = 4403,
28 | Ewer = 4405,
29 | Spire = 4406,
30 | EarthlyStar = 7439,
31 | Malefic3 = 7442,
32 | MinorArcana = 7443,
33 | SleeveDraw = 7448,
34 | Divination = 16552,
35 | CelestialOpposition = 16553,
36 | Malefic4 = 16555,
37 | Horoscope = 16557,
38 | NeutralSect = 16559,
39 | Play = 17055,
40 | CrownPlay = 25869,
41 | Astrodyne = 25870,
42 | FallMalefic = 25871,
43 | Gravity2 = 25872,
44 | Exaltation = 25873,
45 | Macrocosmos = 25874;
46 |
47 | public static class Buffs
48 | {
49 | public const ushort
50 | ClarifyingDraw = 2713;
51 | }
52 |
53 | public static class Debuffs
54 | {
55 | public const ushort
56 | Placeholder = 0;
57 | }
58 |
59 | public static class Levels
60 | {
61 | public const byte
62 | Ascend = 12,
63 | Benefic2 = 26,
64 | Draw = 30,
65 | Redraw = 40,
66 | Astrodyne = 50,
67 | MinorArcana = 70,
68 | CrownPlay = 70;
69 | }
70 | }
71 |
72 | internal class AstrologianMalefic : CustomCombo
73 | {
74 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.AstrologianMaleficDrawFeature;
75 |
76 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
77 | {
78 | if (actionID == AST.Malefic || actionID == AST.Malefic2 || actionID == AST.Malefic3 || actionID == AST.Malefic4 || actionID == AST.FallMalefic)
79 | {
80 | var gauge = GetJobGauge();
81 |
82 | if (level >= AST.Levels.Draw && gauge.DrawnCard == CardType.NONE && HasCharges(AST.Draw))
83 | return AST.Draw;
84 | }
85 |
86 | return actionID;
87 | }
88 | }
89 |
90 | internal class AstrologianGravity : CustomCombo
91 | {
92 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.AstrologianGravityDrawFeature;
93 |
94 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
95 | {
96 | if (actionID == AST.Gravity || actionID == AST.Gravity2)
97 | {
98 | var gauge = GetJobGauge();
99 |
100 | if (level >= AST.Levels.Draw && gauge.DrawnCard == CardType.NONE && HasCharges(AST.Draw))
101 | return AST.Draw;
102 | }
103 |
104 | return actionID;
105 | }
106 | }
107 |
108 | internal class AstrologianPlay : CustomCombo
109 | {
110 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.AstAny;
111 |
112 | private Dictionary CardSeals { get; } = new Dictionary
113 | {
114 | { CardType.BALANCE, SealType.SUN },
115 | { CardType.BOLE, SealType.SUN },
116 | { CardType.ARROW, SealType.MOON },
117 | { CardType.EWER, SealType.MOON },
118 | { CardType.SPEAR, SealType.CELESTIAL },
119 | { CardType.SPIRE, SealType.CELESTIAL },
120 | };
121 |
122 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
123 | {
124 | if (actionID == AST.Play)
125 | {
126 | var gauge = GetJobGauge();
127 |
128 | if (IsEnabled(CustomComboPreset.AstrologianPlayAstrodyneFeature))
129 | {
130 | if (level >= AST.Levels.Astrodyne && !gauge.ContainsSeal(SealType.NONE))
131 | return AST.Astrodyne;
132 | }
133 |
134 | if (IsEnabled(CustomComboPreset.AstrologianPlayDrawFeature))
135 | {
136 | if (IsEnabled(CustomComboPreset.AstrologianPlayDrawAstrodyneFeature))
137 | {
138 | var draw = GetCooldown(AST.Draw);
139 |
140 | if (level >= AST.Levels.Astrodyne && !gauge.ContainsSeal(SealType.NONE) && (draw.RemainingCharges == 0 || gauge.DrawnCard != CardType.NONE))
141 | return AST.Astrodyne;
142 | }
143 |
144 | if (level >= AST.Levels.Draw && gauge.DrawnCard == CardType.NONE)
145 | return AST.Draw;
146 | }
147 |
148 | if (IsEnabled(CustomComboPreset.AstrologianPlayRedrawFeature))
149 | {
150 | // Use redraw if and only if the player has has a card drawn and the drawn card is a seal type the
151 | // player already has. Note that there is no check here to see if the player already has 3 seals.
152 | // While players should never use Draw at 3 seals, if the player has not enabled the Play to Astrodyne
153 | // or Play to Draw to Astrodyne feature, we should still handle the Redraw check as normal, since the
154 | // ONLY reason to use Play at 3 seals is to try to fish for the 3-different-seals Astrodyne, even though
155 | // that's an unmitigated DPS loss over using Astrodyne at only 1 or 2 seals.
156 |
157 | if (level >= AST.Levels.Redraw && gauge.DrawnCard != CardType.NONE && HasEffect(AST.Buffs.ClarifyingDraw))
158 | {
159 | var cardSeal = this.CardSeals.GetValueOrDefault(gauge.DrawnCard, SealType.NONE);
160 |
161 | if (gauge.ContainsSeal(cardSeal))
162 | return AST.Redraw;
163 | }
164 | }
165 | }
166 |
167 | return actionID;
168 | }
169 | }
170 |
171 | internal class AstrologianDraw : CustomCombo
172 | {
173 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.AstrologianDrawLockoutFeature;
174 |
175 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
176 | {
177 | if (actionID == AST.Draw)
178 | {
179 | var gauge = GetJobGauge();
180 |
181 | if (gauge.DrawnCard != CardType.NONE)
182 | // Malefic4
183 | return OriginalHook(AST.Malefic);
184 | }
185 |
186 | return actionID;
187 | }
188 | }
189 |
190 | internal class AstrologianBenefic2 : CustomCombo
191 | {
192 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.AstrologianBeneficSyncFeature;
193 |
194 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
195 | {
196 | if (actionID == AST.Benefic2)
197 | {
198 | if (level < AST.Levels.Benefic2)
199 | return AST.Benefic;
200 | }
201 |
202 | return actionID;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/SCH.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class SCH
6 | {
7 | public const byte ClassID = 15;
8 | public const byte JobID = 28;
9 |
10 | public const uint
11 | Aetherflow = 166,
12 | EnergyDrain = 167,
13 | Resurrection = 173,
14 | Adloquium = 185,
15 | SacredSoil = 188,
16 | Lustrate = 189,
17 | Physick = 190,
18 | Indomitability = 3583,
19 | DeploymentTactics = 3585,
20 | EmergencyTactics = 3586,
21 | Dissipation = 3587,
22 | Excogitation = 7434,
23 | ChainStratagem = 7436,
24 | Aetherpact = 7437,
25 | WhisperingDawn = 16537,
26 | FeyIllumination = 16538,
27 | Recitation = 16542,
28 | FeyBless = 16543,
29 | SummonSeraph = 16545,
30 | Consolation = 16546,
31 | SummonEos = 17215,
32 | SummonSelene = 17216,
33 | Ruin2 = 17870;
34 |
35 | public static class Buffs
36 | {
37 | public const ushort
38 | Dissipation = 791,
39 | Recitation = 1896;
40 | }
41 |
42 | public static class Debuffs
43 | {
44 | public const ushort
45 | Placeholder = 0;
46 | }
47 |
48 | public static class Levels
49 | {
50 | public const byte
51 | Resurrection = 12,
52 | Adloquium = 30,
53 | Aetherflow = 45,
54 | Lustrate = 45,
55 | Excogitation = 62,
56 | ChainStratagem = 66,
57 | Recitation = 74,
58 | Consolation = 80,
59 | SummonSeraph = 80;
60 | }
61 | }
62 |
63 | internal class ScholarFeyBless : CustomCombo
64 | {
65 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.ScholarSeraphConsolationFeature;
66 |
67 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
68 | {
69 | if (actionID == SCH.FeyBless)
70 | {
71 | var gauge = GetJobGauge();
72 |
73 | if (level >= SCH.Levels.Consolation && gauge.SeraphTimer > 0)
74 | return SCH.Consolation;
75 | }
76 |
77 | return actionID;
78 | }
79 | }
80 |
81 | internal class ScholarExcogitation : CustomCombo
82 | {
83 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SchAny;
84 |
85 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
86 | {
87 | if (actionID == SCH.Excogitation)
88 | {
89 | if (IsEnabled(CustomComboPreset.ScholarExcogitationRecitationFeature))
90 | {
91 | if (level >= SCH.Levels.Recitation && IsOffCooldown(SCH.Recitation))
92 | return SCH.Recitation;
93 | }
94 |
95 | if (IsEnabled(CustomComboPreset.ScholarExcogitationLustrateFeature))
96 | {
97 | if (level < SCH.Levels.Excogitation || IsOnCooldown(SCH.Excogitation))
98 | return SCH.Lustrate;
99 | }
100 | }
101 |
102 | return actionID;
103 | }
104 | }
105 |
106 | internal class ScholarEnergyDrain : CustomCombo
107 | {
108 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.ScholarEnergyDrainAetherflowFeature;
109 |
110 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
111 | {
112 | if (actionID == SCH.EnergyDrain)
113 | {
114 | var gauge = GetJobGauge();
115 |
116 | if (level >= SCH.Levels.Aetherflow && gauge.Aetherflow == 0)
117 | return SCH.Aetherflow;
118 | }
119 |
120 | return actionID;
121 | }
122 | }
123 |
124 | internal class ScholarLustrate : CustomCombo
125 | {
126 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SchAny;
127 |
128 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
129 | {
130 | if (actionID == SCH.Lustrate)
131 | {
132 | var gauge = GetJobGauge();
133 |
134 | if (IsEnabled(CustomComboPreset.ScholarLustrateRecitationFeature))
135 | {
136 | if (level >= SCH.Levels.Recitation && IsOffCooldown(SCH.Recitation))
137 | return SCH.Recitation;
138 | }
139 |
140 | if (IsEnabled(CustomComboPreset.ScholarLustrateExcogitationFeature))
141 | {
142 | if (level >= SCH.Levels.Excogitation && IsOffCooldown(SCH.Excogitation))
143 | return SCH.Excogitation;
144 | }
145 |
146 | if (IsEnabled(CustomComboPreset.ScholarLustrateAetherflowFeature))
147 | {
148 | if (level >= SCH.Levels.Aetherflow && gauge.Aetherflow == 0)
149 | return SCH.Aetherflow;
150 | }
151 | }
152 |
153 | return actionID;
154 | }
155 | }
156 |
157 | internal class ScholarIndomitability : CustomCombo
158 | {
159 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.ScholarIndomAetherflowFeature;
160 |
161 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
162 | {
163 | if (actionID == SCH.Indomitability)
164 | {
165 | var gauge = GetJobGauge();
166 |
167 | if (level >= SCH.Levels.Aetherflow && gauge.Aetherflow == 0 && !HasEffect(SCH.Buffs.Recitation))
168 | return SCH.Aetherflow;
169 | }
170 |
171 | return actionID;
172 | }
173 | }
174 |
175 | internal class ScholarSacredSoil : CustomCombo
176 | {
177 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.ScholarSacredSoilAetherflowFeature;
178 |
179 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
180 | {
181 | if (actionID == SCH.SacredSoil)
182 | {
183 | var gauge = GetJobGauge();
184 |
185 | if (level >= SCH.Levels.Aetherflow && gauge.Aetherflow == 0)
186 | return SCH.Aetherflow;
187 | }
188 |
189 | return actionID;
190 | }
191 | }
192 |
193 | internal class ScholarSummon : CustomCombo
194 | {
195 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.ScholarSeraphFeature;
196 |
197 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
198 | {
199 | if (actionID == SCH.SummonEos || actionID == SCH.SummonSelene)
200 | {
201 | var gauge = GetJobGauge();
202 |
203 | if (gauge.SeraphTimer != 0 || HasPetPresent())
204 | // Consolation
205 | return OriginalHook(SCH.SummonSeraph);
206 | }
207 |
208 | return actionID;
209 | }
210 | }
211 |
212 | internal class ScholarAdloquium : CustomCombo
213 | {
214 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.ScholarAdloquiumSyncFeature;
215 |
216 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
217 | {
218 | if (actionID == SCH.Adloquium)
219 | {
220 | if (level < SCH.Levels.Adloquium)
221 | return SCH.Physick;
222 | }
223 |
224 | return actionID;
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/DRK.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class DRK
6 | {
7 | public const byte JobID = 32;
8 |
9 | public const uint
10 | HardSlash = 3617,
11 | Unleash = 3621,
12 | SyphonStrike = 3623,
13 | Souleater = 3632,
14 | BloodWeapon = 3625,
15 | SaltedEarth = 3639,
16 | AbyssalDrain = 3641,
17 | CarveAndSpit = 3643,
18 | Quietus = 7391,
19 | Bloodspiller = 7392,
20 | FloodOfDarkness = 16466,
21 | EdgeOfDarkness = 16467,
22 | StalwartSoul = 16468,
23 | FloodOfShadow = 16469,
24 | EdgeOfShadow = 16470,
25 | LivingShadow = 16472,
26 | SaltAndDarkness = 25755,
27 | Shadowbringer = 25757;
28 |
29 | public static class Buffs
30 | {
31 | public const ushort
32 | BloodWeapon = 742,
33 | Darkside = 751,
34 | Delirium = 1972;
35 | }
36 |
37 | public static class Debuffs
38 | {
39 | public const ushort
40 | Placeholder = 0;
41 | }
42 |
43 | public static class Levels
44 | {
45 | public const byte
46 | SyphonStrike = 2,
47 | Souleater = 26,
48 | FloodOfDarkness = 30,
49 | BloodWeapon = 35,
50 | EdgeOfDarkness = 40,
51 | StalwartSoul = 40,
52 | SaltedEarth = 52,
53 | AbyssalDrain = 56,
54 | CarveAndSpit = 60,
55 | Bloodspiller = 62,
56 | Quietus = 64,
57 | Delirium = 68,
58 | Shadow = 74,
59 | LivingShadow = 80,
60 | SaltAndDarkness = 86,
61 | Shadowbringer = 90;
62 | }
63 | }
64 |
65 | internal class DarkSouleater : CustomCombo
66 | {
67 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrkAny;
68 |
69 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
70 | {
71 | if (actionID == DRK.Souleater)
72 | {
73 | var gauge = GetJobGauge();
74 |
75 | if (IsEnabled(CustomComboPreset.DarkDeliriumFeature))
76 | {
77 | if (level >= DRK.Levels.Bloodspiller && level >= DRK.Levels.Delirium && HasEffect(DRK.Buffs.Delirium))
78 | return DRK.Bloodspiller;
79 | }
80 |
81 | if (IsEnabled(CustomComboPreset.DarkSouleaterCombo))
82 | {
83 | if (IsEnabled(CustomComboPreset.DarkSouleaterOvercapFeature))
84 | {
85 | if (level >= DRK.Levels.Bloodspiller && gauge.Blood > 90 && HasEffect(DRK.Buffs.BloodWeapon))
86 | return DRK.Bloodspiller;
87 | }
88 |
89 | if (comboTime > 0)
90 | {
91 | if (lastComboMove == DRK.SyphonStrike && level >= DRK.Levels.Souleater)
92 | {
93 | if (IsEnabled(CustomComboPreset.DarkSouleaterOvercapFeature))
94 | {
95 | if (level >= DRK.Levels.Bloodspiller && (gauge.Blood > 80 || (gauge.Blood > 70 && HasEffect(DRK.Buffs.BloodWeapon))))
96 | return DRK.Bloodspiller;
97 | }
98 |
99 | return DRK.Souleater;
100 | }
101 |
102 | if (lastComboMove == DRK.HardSlash && level >= DRK.Levels.SyphonStrike)
103 | return DRK.SyphonStrike;
104 | }
105 |
106 | return DRK.HardSlash;
107 | }
108 | }
109 |
110 | return actionID;
111 | }
112 | }
113 |
114 | internal class DarkStalwartSoul : CustomCombo
115 | {
116 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrkAny;
117 |
118 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
119 | {
120 | if (actionID == DRK.StalwartSoul)
121 | {
122 | var gauge = GetJobGauge();
123 |
124 | if (IsEnabled(CustomComboPreset.DarkDeliriumFeature))
125 | {
126 | if (level >= DRK.Levels.Quietus && level >= DRK.Levels.Delirium && HasEffect(DRK.Buffs.Delirium))
127 | return DRK.Quietus;
128 | }
129 |
130 | if (IsEnabled(CustomComboPreset.DarkStalwartSoulCombo))
131 | {
132 | if (IsEnabled(CustomComboPreset.DarkStalwartSoulOvercapFeature))
133 | {
134 | if (level >= DRK.Levels.Quietus && gauge.Blood > 90 && HasEffect(DRK.Buffs.BloodWeapon))
135 | return DRK.Quietus;
136 | }
137 |
138 | if (comboTime > 0)
139 | {
140 | if (lastComboMove == DRK.Unleash && level >= DRK.Levels.StalwartSoul)
141 | {
142 | if (IsEnabled(CustomComboPreset.DarkStalwartSoulOvercapFeature))
143 | {
144 | if (level >= DRK.Levels.Quietus && (gauge.Blood > 80 || (gauge.Blood > 70 && HasEffect(DRK.Buffs.BloodWeapon))))
145 | return DRK.Quietus;
146 | }
147 |
148 | return DRK.StalwartSoul;
149 | }
150 | }
151 |
152 | return DRK.Unleash;
153 | }
154 | }
155 |
156 | return actionID;
157 | }
158 | }
159 |
160 | internal class DarkCarveAndSpitAbyssalDrain : CustomCombo
161 | {
162 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrkAny;
163 |
164 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
165 | {
166 | if (actionID == DRK.CarveAndSpit || actionID == DRK.AbyssalDrain)
167 | {
168 | if (IsEnabled(CustomComboPreset.DarkBloodWeaponFeature))
169 | {
170 | if (actionID == DRK.AbyssalDrain && level < DRK.Levels.AbyssalDrain)
171 | return DRK.BloodWeapon;
172 |
173 | if (actionID == DRK.CarveAndSpit && level < DRK.Levels.CarveAndSpit)
174 | return DRK.BloodWeapon;
175 |
176 | if (level >= DRK.Levels.BloodWeapon && IsOffCooldown(DRK.BloodWeapon))
177 | return DRK.BloodWeapon;
178 | }
179 | }
180 |
181 | return actionID;
182 | }
183 | }
184 |
185 | internal class DarkQuietusBloodspiller : CustomCombo
186 | {
187 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrkAny;
188 |
189 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
190 | {
191 | if (actionID == DRK.Quietus || actionID == DRK.Bloodspiller)
192 | {
193 | var gauge = GetJobGauge();
194 |
195 | if (IsEnabled(CustomComboPreset.DarkLivingShadowFeature))
196 | {
197 | if (level >= DRK.Levels.LivingShadow && gauge.Blood >= 50 && IsOffCooldown(DRK.LivingShadow))
198 | return DRK.LivingShadow;
199 | }
200 | }
201 |
202 | return actionID;
203 | }
204 | }
205 |
206 | internal class DarkLivingShadow : CustomCombo
207 | {
208 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrkAny;
209 |
210 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
211 | {
212 | if (actionID == DRK.LivingShadow)
213 | {
214 | var gauge = GetJobGauge();
215 |
216 | if (IsEnabled(CustomComboPreset.DarkLivingShadowbringerFeature))
217 | {
218 | if (level >= DRK.Levels.Shadowbringer && gauge.ShadowTimeRemaining > 0 && HasCharges(DRK.Shadowbringer))
219 | return DRK.Shadowbringer;
220 | }
221 |
222 | if (IsEnabled(CustomComboPreset.DarkLivingShadowbringerHpFeature))
223 | {
224 | if (level >= DRK.Levels.Shadowbringer && HasCharges(DRK.Shadowbringer) && IsOnCooldown(DRK.LivingShadow))
225 | return DRK.Shadowbringer;
226 | }
227 | }
228 |
229 | return actionID;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/GNB.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class GNB
6 | {
7 | public const byte JobID = 37;
8 |
9 | public const uint
10 | KeenEdge = 16137,
11 | NoMercy = 16138,
12 | BrutalShell = 16139,
13 | DemonSlice = 16141,
14 | SolidBarrel = 16145,
15 | GnashingFang = 16146,
16 | DemonSlaughter = 16149,
17 | SonicBreak = 16153,
18 | Continuation = 16155,
19 | JugularRip = 16156,
20 | AbdomenTear = 16157,
21 | EyeGouge = 16158,
22 | BowShock = 16159,
23 | BurstStrike = 16162,
24 | FatedCircle = 16163,
25 | Bloodfest = 16164,
26 | Hypervelocity = 25759,
27 | DoubleDown = 25760;
28 |
29 | public static class Buffs
30 | {
31 | public const ushort
32 | NoMercy = 1831,
33 | ReadyToRip = 1842,
34 | ReadyToTear = 1843,
35 | ReadyToGouge = 1844,
36 | ReadyToBlast = 2686;
37 | }
38 |
39 | public static class Debuffs
40 | {
41 | public const ushort
42 | BowShock = 1838;
43 | }
44 |
45 | public static class Levels
46 | {
47 | public const byte
48 | NoMercy = 2,
49 | BrutalShell = 4,
50 | SolidBarrel = 26,
51 | BurstStrike = 30,
52 | DemonSlaughter = 40,
53 | SonicBreak = 54,
54 | BowShock = 62,
55 | Continuation = 70,
56 | FatedCircle = 72,
57 | Bloodfest = 76,
58 | EnhancedContinuation = 86,
59 | CartridgeCharge2 = 88,
60 | DoubleDown = 90;
61 | }
62 | }
63 |
64 | internal class GunbreakerSolidBarrel : CustomCombo
65 | {
66 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.GunbreakerSolidBarrelCombo;
67 |
68 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
69 | {
70 | if (actionID == GNB.SolidBarrel)
71 | {
72 | if (comboTime > 0)
73 | {
74 | if (lastComboMove == GNB.BrutalShell && level >= GNB.Levels.SolidBarrel)
75 | {
76 | if (IsEnabled(CustomComboPreset.GunbreakerBurstStrikeFeature))
77 | {
78 | var gauge = GetJobGauge();
79 | var maxAmmo = level >= GNB.Levels.CartridgeCharge2 ? 3 : 2;
80 |
81 | if (IsEnabled(CustomComboPreset.GunbreakerBurstStrikeCont))
82 | {
83 | if (level >= GNB.Levels.EnhancedContinuation && HasEffect(GNB.Buffs.ReadyToBlast))
84 | return GNB.Hypervelocity;
85 | }
86 |
87 | if (level >= GNB.Levels.BurstStrike && gauge.Ammo == maxAmmo)
88 | return GNB.BurstStrike;
89 | }
90 |
91 | return GNB.SolidBarrel;
92 | }
93 |
94 | if (lastComboMove == GNB.KeenEdge && level >= GNB.Levels.BrutalShell)
95 | return GNB.BrutalShell;
96 | }
97 |
98 | return GNB.KeenEdge;
99 | }
100 |
101 | return actionID;
102 | }
103 | }
104 |
105 | internal class GunbreakerGnashingFang : CustomCombo
106 | {
107 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.GunbreakerGnashingFangCont;
108 |
109 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
110 | {
111 | if (actionID == GNB.GnashingFang)
112 | {
113 | if (level >= GNB.Levels.Continuation)
114 | {
115 | if (HasEffect(GNB.Buffs.ReadyToGouge))
116 | return GNB.EyeGouge;
117 |
118 | if (HasEffect(GNB.Buffs.ReadyToTear))
119 | return GNB.AbdomenTear;
120 |
121 | if (HasEffect(GNB.Buffs.ReadyToRip))
122 | return GNB.JugularRip;
123 | }
124 |
125 | // Gnashing Fang > Savage Claw > Wicked Talon
126 | return OriginalHook(GNB.GnashingFang);
127 | }
128 |
129 | return actionID;
130 | }
131 | }
132 |
133 | internal class GunbreakerBurstStrikeFatedCircle : CustomCombo
134 | {
135 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.GnbAny;
136 |
137 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
138 | {
139 | if (actionID == GNB.BurstStrike)
140 | {
141 | if (IsEnabled(CustomComboPreset.GunbreakerBurstStrikeCont))
142 | {
143 | if (level >= GNB.Levels.EnhancedContinuation && HasEffect(GNB.Buffs.ReadyToBlast))
144 | return GNB.Hypervelocity;
145 | }
146 | }
147 |
148 | if (actionID == GNB.BurstStrike || actionID == GNB.FatedCircle)
149 | {
150 | var gauge = GetJobGauge();
151 |
152 | if (IsEnabled(CustomComboPreset.GunbreakerDoubleDownFeature))
153 | {
154 | if (level >= GNB.Levels.DoubleDown && gauge.Ammo >= 2 && IsOffCooldown(GNB.DoubleDown))
155 | return GNB.DoubleDown;
156 | }
157 |
158 | if (IsEnabled(CustomComboPreset.GunbreakerEmptyBloodfestFeature))
159 | {
160 | if (level >= GNB.Levels.Bloodfest && gauge.Ammo == 0)
161 | return GNB.Bloodfest;
162 | }
163 | }
164 |
165 | return actionID;
166 | }
167 | }
168 |
169 | internal class GunbreakerBowShockSonicBreak : CustomCombo
170 | {
171 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.GunbreakerBowShockSonicBreakFeature;
172 |
173 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
174 | {
175 | if (actionID == GNB.BowShock || actionID == GNB.SonicBreak)
176 | {
177 | if (level >= GNB.Levels.BowShock)
178 | return CalcBestAction(actionID, GNB.BowShock, GNB.SonicBreak);
179 | }
180 |
181 | return actionID;
182 | }
183 | }
184 |
185 | internal class GunbreakerDemonSlaughter : CustomCombo
186 | {
187 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.GunbreakerDemonSlaughterCombo;
188 |
189 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
190 | {
191 | if (actionID == GNB.DemonSlaughter)
192 | {
193 | if (comboTime > 0 && lastComboMove == GNB.DemonSlice && level >= GNB.Levels.DemonSlaughter)
194 | {
195 | if (IsEnabled(CustomComboPreset.GunbreakerFatedCircleFeature))
196 | {
197 | var gauge = GetJobGauge();
198 | var maxAmmo = level >= GNB.Levels.CartridgeCharge2 ? 3 : 2;
199 |
200 | if (level >= GNB.Levels.FatedCircle && gauge.Ammo == maxAmmo)
201 | return GNB.FatedCircle;
202 | }
203 |
204 | return GNB.DemonSlaughter;
205 | }
206 |
207 | return GNB.DemonSlice;
208 | }
209 |
210 | return actionID;
211 | }
212 | }
213 |
214 | internal class GunbreakerNoMercy : CustomCombo
215 | {
216 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.GnbAny;
217 |
218 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
219 | {
220 | if (actionID == GNB.NoMercy)
221 | {
222 | var gauge = GetJobGauge();
223 |
224 | if (IsEnabled(CustomComboPreset.GunbreakerNoMercyDoubleDownFeature))
225 | {
226 | if (level >= GNB.Levels.NoMercy && HasEffect(GNB.Buffs.NoMercy))
227 | {
228 | if (level >= GNB.Levels.DoubleDown && gauge.Ammo >= 2 && IsOffCooldown(GNB.DoubleDown))
229 | return GNB.DoubleDown;
230 | }
231 | }
232 |
233 | if (IsEnabled(CustomComboPreset.GunbreakerNoMercyAlwaysDoubleDownFeature))
234 | {
235 | if (level >= GNB.Levels.NoMercy && HasEffect(GNB.Buffs.NoMercy))
236 | {
237 | if (level >= GNB.Levels.DoubleDown)
238 | return GNB.DoubleDown;
239 | }
240 | }
241 |
242 | if (IsEnabled(CustomComboPreset.GunbreakerNoMercyFeature))
243 | {
244 | if (level >= GNB.Levels.NoMercy && HasEffect(GNB.Buffs.NoMercy))
245 | {
246 | if (level >= GNB.Levels.BowShock)
247 | return CalcBestAction(GNB.SonicBreak, GNB.SonicBreak, GNB.BowShock);
248 |
249 | if (level >= GNB.Levels.SonicBreak)
250 | return GNB.SonicBreak;
251 | }
252 | }
253 | }
254 |
255 | return actionID;
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/SGE.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class SGE
6 | {
7 | public const byte JobID = 40;
8 |
9 | public const uint
10 | Dosis = 24283,
11 | Diagnosis = 24284,
12 | Kardia = 24285,
13 | Prognosis = 24286,
14 | Egeiro = 24287,
15 | Physis = 24288,
16 | Phlegma = 24289,
17 | Eukrasia = 24290,
18 | Soteria = 24294,
19 | Druochole = 24296,
20 | Dyskrasia = 24297,
21 | Kerachole = 24298,
22 | Ixochole = 24299,
23 | Zoe = 24300,
24 | Pepsis = 24301,
25 | Physis2 = 24302,
26 | Taurochole = 24303,
27 | Toxikon = 24304,
28 | Haima = 24305,
29 | Phlegma2 = 24307,
30 | Rhizomata = 24309,
31 | Holos = 24310,
32 | Panhaima = 24311,
33 | Phlegma3 = 24313,
34 | Krasis = 24317,
35 | Pneuma = 24318;
36 |
37 | public static class Buffs
38 | {
39 | public const ushort
40 | Kardion = 2604;
41 | }
42 |
43 | public static class Debuffs
44 | {
45 | public const ushort
46 | Placeholder = 0;
47 | }
48 |
49 | public static class Levels
50 | {
51 | public const ushort
52 | Dosis = 1,
53 | Prognosis = 10,
54 | Egeiro = 12,
55 | Phlegma = 26,
56 | Soteria = 35,
57 | Druochole = 45,
58 | Dyskrasia = 46,
59 | Kerachole = 50,
60 | Ixochole = 52,
61 | Physis2 = 60,
62 | Taurochole = 62,
63 | Toxicon = 66,
64 | Haima = 70,
65 | Phlegma2 = 72,
66 | Dosis2 = 72,
67 | Rhizomata = 74,
68 | Holos = 76,
69 | Panhaima = 80,
70 | Phlegma3 = 82,
71 | Dosis3 = 82,
72 | Krasis = 86,
73 | Pneuma = 90;
74 | }
75 | }
76 |
77 | internal class SageDosis : CustomCombo
78 | {
79 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
80 |
81 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
82 | {
83 | if (actionID == SGE.Dosis)
84 | {
85 | if (IsEnabled(CustomComboPreset.SageDosisKardiaFeature))
86 | {
87 | if (!HasEffect(SGE.Buffs.Kardion))
88 | return SGE.Kardia;
89 | }
90 | }
91 |
92 | return actionID;
93 | }
94 | }
95 |
96 | internal class SageToxikon : CustomCombo
97 | {
98 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
99 |
100 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
101 | {
102 | if (actionID == SGE.Toxikon)
103 | {
104 | if (IsEnabled(CustomComboPreset.SageToxikonPhlegma))
105 | {
106 | var phlegma =
107 | level >= SGE.Levels.Phlegma3 ? SGE.Phlegma3 :
108 | level >= SGE.Levels.Phlegma2 ? SGE.Phlegma2 :
109 | level >= SGE.Levels.Phlegma ? SGE.Phlegma : 0;
110 |
111 | if (phlegma != 0 && HasCharges(phlegma))
112 | return OriginalHook(SGE.Phlegma);
113 | }
114 | }
115 |
116 | return actionID;
117 | }
118 | }
119 |
120 | internal class SageSoteria : CustomCombo
121 | {
122 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
123 |
124 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
125 | {
126 | if (actionID == SGE.Soteria)
127 | {
128 | if (IsEnabled(CustomComboPreset.SageSoteriaKardionFeature))
129 | {
130 | if (!HasEffect(SGE.Buffs.Kardion) && IsOffCooldown(SGE.Soteria))
131 | return SGE.Kardia;
132 | }
133 | }
134 |
135 | return actionID;
136 | }
137 | }
138 |
139 | internal class SageTaurochole : CustomCombo
140 | {
141 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
142 |
143 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
144 | {
145 | if (actionID == SGE.Taurochole)
146 | {
147 | var gauge = GetJobGauge();
148 |
149 | if (IsEnabled(CustomComboPreset.SageTaurocholeRhizomataFeature))
150 | {
151 | if (level >= SGE.Levels.Rhizomata && gauge.Addersgall == 0)
152 | return SGE.Rhizomata;
153 | }
154 |
155 | if (IsEnabled(CustomComboPreset.SageTaurocholeDruocholeFeature))
156 | {
157 | if (level >= SGE.Levels.Taurochole && IsOffCooldown(SGE.Taurochole))
158 | return SGE.Taurochole;
159 |
160 | return SGE.Druochole;
161 | }
162 | }
163 |
164 | return actionID;
165 | }
166 | }
167 |
168 | internal class SageDruochole : CustomCombo
169 | {
170 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
171 |
172 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
173 | {
174 | if (actionID == SGE.Druochole)
175 | {
176 | var gauge = GetJobGauge();
177 |
178 | if (IsEnabled(CustomComboPreset.SageDruocholeRhizomataFeature))
179 | {
180 | if (level >= SGE.Levels.Rhizomata && gauge.Addersgall == 0)
181 | return SGE.Rhizomata;
182 | }
183 |
184 | if (IsEnabled(CustomComboPreset.SageDruocholeTaurocholeFeature))
185 | {
186 | if (level >= SGE.Levels.Taurochole && IsOffCooldown(SGE.Taurochole))
187 | return SGE.Taurochole;
188 | }
189 | }
190 |
191 | return actionID;
192 | }
193 | }
194 |
195 | internal class SageIxochole : CustomCombo
196 | {
197 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
198 |
199 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
200 | {
201 | if (actionID == SGE.Ixochole)
202 | {
203 | var gauge = GetJobGauge();
204 |
205 | if (IsEnabled(CustomComboPreset.SageIxocholeRhizomataFeature))
206 | {
207 | if (level >= SGE.Levels.Rhizomata && gauge.Addersgall == 0)
208 | return SGE.Rhizomata;
209 | }
210 | }
211 |
212 | return actionID;
213 | }
214 | }
215 |
216 | internal class SageKerachole : CustomCombo
217 | {
218 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
219 |
220 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
221 | {
222 | if (actionID == SGE.Kerachole)
223 | {
224 | var gauge = GetJobGauge();
225 |
226 | if (IsEnabled(CustomComboPreset.SageKeracholaRhizomataFeature))
227 | {
228 | if (level >= SGE.Levels.Rhizomata && gauge.Addersgall == 0)
229 | return SGE.Rhizomata;
230 | }
231 | }
232 |
233 | return actionID;
234 | }
235 | }
236 |
237 | internal class SagePhlegma : CustomCombo
238 | {
239 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SgeAny;
240 |
241 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
242 | {
243 | if (actionID == SGE.Phlegma || actionID == SGE.Phlegma2 || actionID == SGE.Phlegma3)
244 | {
245 | var gauge = GetJobGauge();
246 |
247 | if (IsEnabled(CustomComboPreset.SagePhlegmaDyskrasia))
248 | {
249 | if (level >= SGE.Levels.Dyskrasia && HasNoTarget())
250 | return OriginalHook(SGE.Dyskrasia);
251 | }
252 |
253 | if (IsEnabled(CustomComboPreset.SagePhlegmaToxicon))
254 | {
255 | var phlegma =
256 | level >= SGE.Levels.Phlegma3 ? SGE.Phlegma3 :
257 | level >= SGE.Levels.Phlegma2 ? SGE.Phlegma2 :
258 | level >= SGE.Levels.Phlegma ? SGE.Phlegma : 0;
259 |
260 | if (level >= SGE.Levels.Toxicon && phlegma != 0 && HasNoCharges(phlegma) && gauge.Addersting > 0)
261 | return OriginalHook(SGE.Toxikon);
262 | }
263 |
264 | if (IsEnabled(CustomComboPreset.SagePhlegmaDyskrasia))
265 | {
266 | var phlegma =
267 | level >= SGE.Levels.Phlegma3 ? SGE.Phlegma3 :
268 | level >= SGE.Levels.Phlegma2 ? SGE.Phlegma2 :
269 | level >= SGE.Levels.Phlegma ? SGE.Phlegma : 0;
270 |
271 | if (level >= SGE.Levels.Dyskrasia && phlegma != 0 && HasNoCharges(phlegma))
272 | return OriginalHook(SGE.Dyskrasia);
273 | }
274 | }
275 |
276 | return actionID;
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Interface/ConfigWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Numerics;
5 |
6 | using Dalamud.Interface;
7 | using Dalamud.Interface.Colors;
8 | using Dalamud.Interface.Windowing;
9 | using Dalamud.Utility;
10 | using ImGuiNET;
11 | using XIVComboExpandedPlugin.Attributes;
12 |
13 | namespace XIVComboExpandedPlugin.Interface;
14 |
15 | ///
16 | /// Plugin configuration window.
17 | ///
18 | internal class ConfigWindow : Window
19 | {
20 | private readonly Dictionary> groupedPresets;
21 | private readonly Dictionary presetChildren;
22 | private readonly Vector4 shadedColor = new(0.68f, 0.68f, 0.68f, 1.0f);
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | public ConfigWindow()
28 | : base("Custom Combo Setup")
29 | {
30 | this.RespectCloseHotkey = true;
31 |
32 | this.groupedPresets = Enum
33 | .GetValues()
34 | .Where(preset => (int)preset > 100 && preset != CustomComboPreset.Disabled)
35 | .Select(preset => (Preset: preset, Info: preset.GetAttribute()))
36 | .Where(tpl => tpl.Info != null && Service.Configuration.GetParent(tpl.Preset) == null)
37 | .OrderBy(tpl => tpl.Info.JobName)
38 | .ThenBy(tpl => tpl.Info.Order)
39 | .GroupBy(tpl => tpl.Info.JobName)
40 | .ToDictionary(
41 | tpl => tpl.Key,
42 | tpl => tpl.ToList());
43 |
44 | var childCombos = Enum.GetValues().ToDictionary(
45 | tpl => tpl,
46 | tpl => new List());
47 |
48 | foreach (var preset in Enum.GetValues())
49 | {
50 | var parent = preset.GetAttribute()?.ParentPreset;
51 | if (parent != null)
52 | childCombos[parent.Value].Add(preset);
53 | }
54 |
55 | this.presetChildren = childCombos.ToDictionary(
56 | kvp => kvp.Key,
57 | kvp => kvp.Value
58 | .Select(preset => (Preset: preset, Info: preset.GetAttribute()))
59 | .OrderBy(tpl => tpl.Info.Order).ToArray());
60 |
61 | this.SizeCondition = ImGuiCond.FirstUseEver;
62 | this.Size = new Vector2(740, 490);
63 | }
64 |
65 | ///
66 | public override void Draw()
67 | {
68 | ImGui.Text("This window allows you to enable and disable custom combos to your liking.");
69 |
70 | var showSecrets = Service.Configuration.EnableSecretCombos;
71 | if (ImGui.Checkbox("Enable secret forbidden knowledge", ref showSecrets))
72 | {
73 | Service.Configuration.EnableSecretCombos = showSecrets;
74 | Service.Configuration.Save();
75 | }
76 |
77 | if (ImGui.IsItemHovered())
78 | {
79 | ImGui.BeginTooltip();
80 | ImGui.TextUnformatted("Combos too dangerous for the common folk");
81 | ImGui.EndTooltip();
82 | }
83 |
84 | var hideChildren = Service.Configuration.HideChildren;
85 | if (ImGui.Checkbox("Hide children of disabled combos and features", ref hideChildren))
86 | {
87 | Service.Configuration.HideChildren = hideChildren;
88 | Service.Configuration.Save();
89 | }
90 |
91 | ImGui.BeginChild("scrolling", new Vector2(0, -1), true);
92 |
93 | ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 5));
94 |
95 | var i = 1;
96 |
97 | foreach (var jobName in this.groupedPresets.Keys)
98 | {
99 | if (ImGui.CollapsingHeader(jobName))
100 | {
101 | foreach (var (preset, info) in this.groupedPresets[jobName])
102 | {
103 | this.DrawPreset(preset, info, ref i);
104 | }
105 | }
106 | else
107 | {
108 | i += this.groupedPresets[jobName].Count;
109 | }
110 | }
111 |
112 | ImGui.PopStyleVar();
113 |
114 | ImGui.EndChild();
115 | }
116 |
117 | private void DrawPreset(CustomComboPreset preset, CustomComboInfoAttribute info, ref int i)
118 | {
119 | var enabled = Service.Configuration.IsEnabled(preset);
120 | var secret = Service.Configuration.IsSecret(preset);
121 | var showSecrets = Service.Configuration.EnableSecretCombos;
122 | var conflicts = Service.Configuration.GetConflicts(preset);
123 | var parent = Service.Configuration.GetParent(preset);
124 |
125 | if (secret && !showSecrets)
126 | return;
127 |
128 | ImGui.PushItemWidth(200);
129 |
130 | if (ImGui.Checkbox(info.FancyName, ref enabled))
131 | {
132 | if (enabled)
133 | {
134 | this.EnableParentPresets(preset);
135 | Service.Configuration.EnabledActions.Add(preset);
136 | foreach (var conflict in conflicts)
137 | {
138 | Service.Configuration.EnabledActions.Remove(conflict);
139 | }
140 | }
141 | else
142 | {
143 | Service.Configuration.EnabledActions.Remove(preset);
144 | }
145 |
146 | Service.Configuration.Save();
147 | }
148 |
149 | if (secret)
150 | {
151 | ImGui.SameLine();
152 | ImGui.Text(" ");
153 | ImGui.SameLine();
154 | ImGui.PushFont(UiBuilder.IconFont);
155 | ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
156 | ImGui.Text(FontAwesomeIcon.Star.ToIconString());
157 | ImGui.PopStyleColor();
158 | ImGui.PopFont();
159 |
160 | if (ImGui.IsItemHovered())
161 | {
162 | ImGui.BeginTooltip();
163 | ImGui.TextUnformatted("Secret");
164 | ImGui.EndTooltip();
165 | }
166 | }
167 |
168 | ImGui.PopItemWidth();
169 |
170 | ImGui.PushStyleColor(ImGuiCol.Text, this.shadedColor);
171 | ImGui.TextWrapped($"#{i}: {info.Description}");
172 | ImGui.PopStyleColor();
173 | ImGui.Spacing();
174 |
175 | if (conflicts.Length > 0)
176 | {
177 | var conflictText = conflicts.Select(conflict =>
178 | {
179 | if (!showSecrets && Service.Configuration.IsSecret(conflict))
180 | return string.Empty;
181 |
182 | var conflictInfo = conflict.GetAttribute();
183 | return $"\n - {conflictInfo.FancyName}";
184 | }).Aggregate((t1, t2) => $"{t1}{t2}");
185 |
186 | if (conflictText.Length > 0)
187 | {
188 | ImGui.TextColored(this.shadedColor, $"Conflicts with: {conflictText}");
189 | ImGui.Spacing();
190 | }
191 | }
192 |
193 | if (preset == CustomComboPreset.DancerDanceComboCompatibility && enabled)
194 | {
195 | var actions = Service.Configuration.DancerDanceCompatActionIDs.Cast().ToArray();
196 |
197 | var inputChanged = false;
198 | inputChanged |= ImGui.InputInt("Emboite (Red) ActionID", ref actions[0], 0);
199 | inputChanged |= ImGui.InputInt("Entrechat (Blue) ActionID", ref actions[1], 0);
200 | inputChanged |= ImGui.InputInt("Jete (Green) ActionID", ref actions[2], 0);
201 | inputChanged |= ImGui.InputInt("Pirouette (Yellow) ActionID", ref actions[3], 0);
202 |
203 | if (inputChanged)
204 | {
205 | Service.Configuration.DancerDanceCompatActionIDs = actions.Cast().ToArray();
206 | Service.Configuration.Save();
207 | }
208 |
209 | ImGui.Spacing();
210 | }
211 |
212 | i++;
213 |
214 | var hideChildren = Service.Configuration.HideChildren;
215 | if (enabled || !hideChildren)
216 | {
217 | var children = this.presetChildren[preset];
218 | if (children.Length > 0)
219 | {
220 | ImGui.Indent();
221 |
222 | foreach (var (childPreset, childInfo) in children)
223 | this.DrawPreset(childPreset, childInfo, ref i);
224 |
225 | ImGui.Unindent();
226 | }
227 | }
228 | }
229 |
230 | ///
231 | /// Iterates up a preset's parent tree, enabling each of them.
232 | ///
233 | /// Combo preset to enabled.
234 | private void EnableParentPresets(CustomComboPreset preset)
235 | {
236 | var parentMaybe = Service.Configuration.GetParent(preset);
237 | while (parentMaybe != null)
238 | {
239 | var parent = parentMaybe.Value;
240 |
241 | if (!Service.Configuration.EnabledActions.Contains(parent))
242 | {
243 | Service.Configuration.EnabledActions.Add(parent);
244 | foreach (var conflict in Service.Configuration.GetConflicts(parent))
245 | {
246 | Service.Configuration.EnabledActions.Remove(conflict);
247 | }
248 | }
249 |
250 | parentMaybe = Service.Configuration.GetParent(parent);
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/MCH.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Dalamud.Game.ClientState.JobGauge.Types;
4 |
5 | namespace XIVComboExpandedPlugin.Combos;
6 |
7 | internal static class MCH
8 | {
9 | public const byte JobID = 31;
10 |
11 | public const uint
12 | // Single target
13 | CleanShot = 2873,
14 | HeatedCleanShot = 7413,
15 | SplitShot = 2866,
16 | HeatedSplitShot = 7411,
17 | SlugShot = 2868,
18 | HeatedSlugshot = 7412,
19 | // Charges
20 | GaussRound = 2874,
21 | Ricochet = 2890,
22 | // AoE
23 | SpreadShot = 2870,
24 | AutoCrossbow = 16497,
25 | Scattergun = 25786,
26 | // Rook
27 | RookAutoturret = 2864,
28 | RookOverdrive = 7415,
29 | AutomatonQueen = 16501,
30 | QueenOverdrive = 16502,
31 | // Other
32 | Wildfire = 2878,
33 | Detonator = 16766,
34 | Hypercharge = 17209,
35 | HeatBlast = 7410,
36 | HotShot = 2872,
37 | Drill = 16498,
38 | Bioblaster = 16499,
39 | AirAnchor = 16500,
40 | Chainsaw = 25788;
41 |
42 | public static class Buffs
43 | {
44 | public const ushort
45 | Placeholder = 0;
46 | }
47 |
48 | public static class Debuffs
49 | {
50 | public const ushort
51 | Placeholder = 0;
52 | }
53 |
54 | public static class Levels
55 | {
56 | public const byte
57 | SlugShot = 2,
58 | GaussRound = 15,
59 | CleanShot = 26,
60 | Hypercharge = 30,
61 | HeatBlast = 35,
62 | RookOverdrive = 40,
63 | Wildfire = 45,
64 | Ricochet = 50,
65 | AutoCrossbow = 52,
66 | HeatedSplitShot = 54,
67 | Drill = 58,
68 | HeatedSlugshot = 60,
69 | HeatedCleanShot = 64,
70 | ChargedActionMastery = 74,
71 | AirAnchor = 76,
72 | QueenOverdrive = 80,
73 | Chainsaw = 90;
74 | }
75 | }
76 |
77 | internal class MachinistCleanShot : CustomCombo
78 | {
79 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistMainCombo;
80 |
81 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
82 | {
83 | if (actionID == MCH.CleanShot || actionID == MCH.HeatedCleanShot)
84 | {
85 | var gauge = GetJobGauge();
86 |
87 | if (IsEnabled(CustomComboPreset.MachinistHypercomboFeature))
88 | {
89 | if (gauge.IsOverheated && level >= MCH.Levels.HeatBlast)
90 | return MCH.HeatBlast;
91 | }
92 |
93 | if (comboTime > 0)
94 | {
95 | if (lastComboMove == MCH.SlugShot && level >= MCH.Levels.CleanShot)
96 | // Heated
97 | return OriginalHook(MCH.CleanShot);
98 |
99 | if (lastComboMove == MCH.SplitShot && level >= MCH.Levels.SlugShot)
100 | // Heated
101 | return OriginalHook(MCH.SlugShot);
102 | }
103 |
104 | // Heated
105 | return OriginalHook(MCH.SplitShot);
106 | }
107 |
108 | return actionID;
109 | }
110 | }
111 |
112 | internal class MachinistGaussRoundRicochet : CustomCombo
113 | {
114 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistGaussRoundRicochetFeature;
115 |
116 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
117 | {
118 | if (actionID == MCH.GaussRound || actionID == MCH.Ricochet)
119 | {
120 | var gauge = GetJobGauge();
121 |
122 | if (IsEnabled(CustomComboPreset.MachinistGaussRoundRicochetFeatureOption))
123 | {
124 | if (!gauge.IsOverheated)
125 | return actionID;
126 | }
127 |
128 | if (level >= MCH.Levels.Ricochet)
129 | return CalcBestAction(actionID, MCH.GaussRound, MCH.Ricochet);
130 |
131 | return MCH.GaussRound;
132 | }
133 |
134 | return actionID;
135 | }
136 | }
137 |
138 | internal class MachinistWildfire : CustomCombo
139 | {
140 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistHyperfireFeature;
141 |
142 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
143 | {
144 | if (actionID == MCH.Hypercharge)
145 | {
146 | if (level >= MCH.Levels.Wildfire && IsOffCooldown(MCH.Wildfire) && HasTarget())
147 | return MCH.Wildfire;
148 |
149 | if (level >= MCH.Levels.Wildfire && IsOnCooldown(MCH.Hypercharge) && !IsOriginal(MCH.Wildfire))
150 | return MCH.Detonator;
151 | }
152 |
153 | return actionID;
154 | }
155 | }
156 |
157 | internal class MachinistHeatBlastAutoCrossbow : CustomCombo
158 | {
159 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistOverheatFeature;
160 |
161 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
162 | {
163 | if (actionID == MCH.HeatBlast || actionID == MCH.AutoCrossbow)
164 | {
165 | var gauge = GetJobGauge();
166 |
167 | if (IsEnabled(CustomComboPreset.MachinistHyperfireFeature))
168 | {
169 | if (level >= MCH.Levels.Wildfire && IsOffCooldown(MCH.Wildfire) && HasTarget())
170 | return MCH.Wildfire;
171 | }
172 |
173 | if (level >= MCH.Levels.Hypercharge && !gauge.IsOverheated)
174 | return MCH.Hypercharge;
175 |
176 | if (level < MCH.Levels.AutoCrossbow)
177 | return MCH.HeatBlast;
178 | }
179 |
180 | return actionID;
181 | }
182 | }
183 |
184 | internal class MachinistSpreadShot : CustomCombo
185 | {
186 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistSpreadShotFeature;
187 |
188 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
189 | {
190 | if (actionID == MCH.SpreadShot || actionID == MCH.Scattergun)
191 | {
192 | var gauge = GetJobGauge();
193 |
194 | if (level >= MCH.Levels.AutoCrossbow && gauge.IsOverheated)
195 | return MCH.AutoCrossbow;
196 | }
197 |
198 | return actionID;
199 | }
200 | }
201 |
202 | internal class MachinistRookAutoturret : CustomCombo
203 | {
204 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistOverdriveFeature;
205 |
206 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
207 | {
208 | if (actionID == MCH.RookAutoturret || actionID == MCH.AutomatonQueen)
209 | {
210 | var gauge = GetJobGauge();
211 |
212 | if (level >= MCH.Levels.RookOverdrive && gauge.IsRobotActive)
213 | // Queen Overdrive
214 | return OriginalHook(MCH.RookOverdrive);
215 | }
216 |
217 | return actionID;
218 | }
219 | }
220 |
221 | internal class MachinistDrillAirAnchorChainsaw : CustomCombo
222 | {
223 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistHotShotDrillChainsawFeature;
224 |
225 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
226 | {
227 | if (actionID == MCH.HotShot || actionID == MCH.AirAnchor || actionID == MCH.Drill || actionID == MCH.Chainsaw)
228 | {
229 | if (level >= MCH.Levels.Chainsaw)
230 | return CalcBestAction(actionID, MCH.Chainsaw, MCH.AirAnchor, MCH.Drill);
231 |
232 | if (level >= MCH.Levels.AirAnchor)
233 | return CalcBestAction(actionID, MCH.AirAnchor, MCH.Drill);
234 |
235 | if (level >= MCH.Levels.Drill)
236 | return CalcBestAction(actionID, MCH.Drill, MCH.HotShot);
237 |
238 | return MCH.HotShot;
239 | }
240 |
241 | return actionID;
242 | }
243 | }
244 |
245 | internal class MachinistAirAnchorChainsaw : CustomCombo
246 | {
247 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistHotShotChainsawFeature;
248 |
249 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
250 | {
251 | if (actionID == MCH.HotShot || actionID == MCH.AirAnchor || actionID == MCH.Chainsaw)
252 | {
253 | if (level >= MCH.Levels.Chainsaw)
254 | return CalcBestAction(actionID, MCH.Chainsaw, MCH.AirAnchor);
255 |
256 | if (level >= MCH.Levels.AirAnchor)
257 | return MCH.AirAnchor;
258 |
259 | return MCH.HotShot;
260 | }
261 |
262 | return actionID;
263 | }
264 | }
265 |
266 | internal class MachinistBioblaster : CustomCombo
267 | {
268 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MachinistBioblasterChainsawFeature;
269 |
270 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
271 | {
272 | if (actionID == MCH.Bioblaster)
273 | {
274 | if (level >= MCH.Levels.Chainsaw)
275 | return CalcBestAction(actionID, MCH.Chainsaw, MCH.Bioblaster);
276 | }
277 |
278 | return actionID;
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/SMN.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class SMN
6 | {
7 | public const byte ClassID = 26;
8 | public const byte JobID = 27;
9 |
10 | public const uint
11 | Ruin = 163,
12 | Ruin2 = 172,
13 | Ruin3 = 3579,
14 | Ruin4 = 7426,
15 | Fester = 181,
16 | Painflare = 3578,
17 | DreadwyrmTrance = 3581,
18 | SummonBahamut = 7427,
19 | EnkindleBahamut = 7429,
20 | EnergySyphon = 16510,
21 | Outburst = 16511,
22 | EnergyDrain = 16508,
23 | SummonCarbuncle = 25798,
24 | RadiantAegis = 25799,
25 | Aethercharge = 25800,
26 | SearingLight = 25801,
27 | AstralFlow = 25822,
28 | TriDisaster = 25826,
29 | CrimsonCyclone = 25835,
30 | MountainBuster = 25836,
31 | Slipstream = 25837,
32 | CrimsonStrike = 25885,
33 | Gemshine = 25883,
34 | PreciousBrilliance = 25884;
35 |
36 | public static class Buffs
37 | {
38 | public const ushort
39 | FurtherRuin = 2701,
40 | IfritsFavor = 2724,
41 | GarudasFavor = 2725,
42 | TitansFavor = 2853;
43 | }
44 |
45 | public static class Debuffs
46 | {
47 | public const ushort
48 | Placeholder = 0;
49 | }
50 |
51 | public static class Levels
52 | {
53 | public const byte
54 | SummonCarbuncle = 2,
55 | RadiantAegis = 2,
56 | Gemshine = 6,
57 | EnergyDrain = 10,
58 | Fester = 10,
59 | PreciousBrilliance = 26,
60 | Painflare = 40,
61 | EnergySyphon = 52,
62 | Ruin3 = 54,
63 | Ruin4 = 62,
64 | SearingLight = 66,
65 | EnkindleBahamut = 70,
66 | Rekindle = 80,
67 | ElementalMastery = 86,
68 | SummonPhoenix = 80;
69 | }
70 | }
71 |
72 | internal class SummonerFester : CustomCombo
73 | {
74 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SummonerEDFesterFeature;
75 |
76 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
77 | {
78 | if (actionID == SMN.Fester)
79 | {
80 | var gauge = GetJobGauge();
81 |
82 | if (level >= SMN.Levels.EnergyDrain && !gauge.HasAetherflowStacks)
83 | return SMN.EnergyDrain;
84 | }
85 |
86 | return actionID;
87 | }
88 | }
89 |
90 | internal class SummonerPainflare : CustomCombo
91 | {
92 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SummonerESPainflareFeature;
93 |
94 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
95 | {
96 | if (actionID == SMN.Painflare)
97 | {
98 | var gauge = GetJobGauge();
99 |
100 | if (level >= SMN.Levels.EnergySyphon && !gauge.HasAetherflowStacks)
101 | return SMN.EnergySyphon;
102 |
103 | if (level >= SMN.Levels.EnergyDrain && !gauge.HasAetherflowStacks)
104 | return SMN.EnergyDrain;
105 |
106 | if (level < SMN.Levels.Painflare)
107 | return SMN.Fester;
108 | }
109 |
110 | return actionID;
111 | }
112 | }
113 |
114 | internal class SummonerRuin : CustomCombo
115 | {
116 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SmnAny;
117 |
118 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
119 | {
120 | if (actionID == SMN.Ruin || actionID == SMN.Ruin2 || actionID == SMN.Ruin3)
121 | {
122 | var gauge = GetJobGauge();
123 |
124 | if (IsEnabled(CustomComboPreset.SummonerRuinTitansFavorFeature))
125 | {
126 | if (level >= SMN.Levels.ElementalMastery && HasEffect(SMN.Buffs.TitansFavor))
127 | return SMN.MountainBuster;
128 | }
129 |
130 | if (IsEnabled(CustomComboPreset.SummonerRuinFeature))
131 | {
132 | if (level >= SMN.Levels.Gemshine)
133 | {
134 | if (gauge.IsIfritAttuned || gauge.IsTitanAttuned || gauge.IsGarudaAttuned)
135 | return OriginalHook(SMN.Gemshine);
136 | }
137 | }
138 |
139 | if (IsEnabled(CustomComboPreset.SummonerFurtherRuinFeature))
140 | {
141 | if (level >= SMN.Levels.Ruin4 && gauge.SummonTimerRemaining == 0 && gauge.AttunmentTimerRemaining == 0 && HasEffect(SMN.Buffs.FurtherRuin))
142 | return SMN.Ruin4;
143 | }
144 | }
145 |
146 | return actionID;
147 | }
148 | }
149 |
150 | internal class SummonerOutburstTriDisaster : CustomCombo
151 | {
152 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SmnAny;
153 |
154 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
155 | {
156 | if (actionID == SMN.Outburst || actionID == SMN.TriDisaster)
157 | {
158 | var gauge = GetJobGauge();
159 |
160 | if (IsEnabled(CustomComboPreset.SummonerOutburstTitansFavorFeature))
161 | {
162 | if (level >= SMN.Levels.ElementalMastery && HasEffect(SMN.Buffs.TitansFavor))
163 | return SMN.MountainBuster;
164 | }
165 |
166 | if (IsEnabled(CustomComboPreset.SummonerOutburstFeature))
167 | {
168 | if (level >= SMN.Levels.PreciousBrilliance)
169 | {
170 | if (gauge.IsIfritAttuned || gauge.IsTitanAttuned || gauge.IsGarudaAttuned)
171 | return OriginalHook(SMN.PreciousBrilliance);
172 | }
173 | }
174 |
175 | if (IsEnabled(CustomComboPreset.SummonerFurtherOutburstFeature))
176 | {
177 | if (level >= SMN.Levels.Ruin4 && gauge.SummonTimerRemaining == 0 && gauge.AttunmentTimerRemaining == 0 && HasEffect(SMN.Buffs.FurtherRuin))
178 | return SMN.Ruin4;
179 | }
180 | }
181 |
182 | return actionID;
183 | }
184 | }
185 |
186 | internal class SummonerGemshinePreciousBrilliance : CustomCombo
187 | {
188 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SmnAny;
189 |
190 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
191 | {
192 | if (actionID == SMN.Gemshine || actionID == SMN.PreciousBrilliance)
193 | {
194 | var gauge = GetJobGauge();
195 |
196 | if (IsEnabled(CustomComboPreset.SummonerShinyTitansFavorFeature))
197 | {
198 | if (level >= SMN.Levels.ElementalMastery && HasEffect(SMN.Buffs.TitansFavor))
199 | return SMN.MountainBuster;
200 | }
201 |
202 | if (IsEnabled(CustomComboPreset.SummonerShinyEnkindleFeature))
203 | {
204 | if (level >= SMN.Levels.EnkindleBahamut && !gauge.IsIfritAttuned && !gauge.IsTitanAttuned && !gauge.IsGarudaAttuned && gauge.SummonTimerRemaining > 0)
205 | // Rekindle
206 | return OriginalHook(SMN.EnkindleBahamut);
207 | }
208 |
209 | if (IsEnabled(CustomComboPreset.SummonerFurtherShinyFeature))
210 | {
211 | if (level >= SMN.Levels.Ruin4 && gauge.SummonTimerRemaining == 0 && gauge.AttunmentTimerRemaining == 0 && HasEffect(SMN.Buffs.FurtherRuin))
212 | return SMN.Ruin4;
213 | }
214 | }
215 |
216 | return actionID;
217 | }
218 | }
219 |
220 | internal class SummonerDemiFeature : CustomCombo
221 | {
222 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SmnAny;
223 |
224 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
225 | {
226 | if (actionID == SMN.Aethercharge || actionID == SMN.DreadwyrmTrance || actionID == SMN.SummonBahamut)
227 | {
228 | if (IsEnabled(CustomComboPreset.SummonerDemiCarbuncleFeature) && !HasPetPresent())
229 | return SMN.SummonCarbuncle;
230 |
231 | var gauge = GetJobGauge();
232 |
233 | if (IsEnabled(CustomComboPreset.SummonerDemiSearingLightFeature))
234 | {
235 | if (level >= SMN.Levels.SearingLight && gauge.IsBahamutReady && InCombat() && IsOffCooldown(SMN.SearingLight))
236 | return SMN.SearingLight;
237 | }
238 |
239 | if (IsEnabled(CustomComboPreset.SummonerDemiEnkindleFeature))
240 | {
241 | if (level >= SMN.Levels.EnkindleBahamut && !gauge.IsIfritAttuned && !gauge.IsTitanAttuned && !gauge.IsGarudaAttuned && gauge.SummonTimerRemaining > 0)
242 | // Rekindle
243 | return OriginalHook(SMN.EnkindleBahamut);
244 | }
245 | }
246 |
247 | return actionID;
248 | }
249 | }
250 |
251 | internal class SummonerRadiantCarbundleFeature : CustomCombo
252 | {
253 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SummonerRadiantCarbuncleFeature;
254 |
255 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
256 | {
257 | if (actionID == SMN.RadiantAegis)
258 | {
259 | var gauge = GetJobGauge();
260 |
261 | if (level >= SMN.Levels.SummonCarbuncle && !HasPetPresent() && gauge.Attunement == 0)
262 | return SMN.SummonCarbuncle;
263 | }
264 |
265 | return actionID;
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/WAR.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Dalamud.Game.ClientState.JobGauge.Types;
4 |
5 | namespace XIVComboExpandedPlugin.Combos;
6 |
7 | internal static class WAR
8 | {
9 | public const byte ClassID = 3;
10 | public const byte JobID = 21;
11 |
12 | public const uint
13 | HeavySwing = 31,
14 | Maim = 37,
15 | Berserk = 38,
16 | ThrillOfBattle = 40,
17 | Overpower = 41,
18 | StormsPath = 42,
19 | StormsEye = 45,
20 | InnerBeast = 49,
21 | SteelCyclone = 51,
22 | Infuriate = 52,
23 | FellCleave = 3549,
24 | Decimate = 3550,
25 | RawIntuition = 3551,
26 | Equilibrium = 3552,
27 | InnerRelease = 7389,
28 | MythrilTempest = 16462,
29 | ChaoticCyclone = 16463,
30 | NascentFlash = 16464,
31 | InnerChaos = 16465,
32 | Bloodwhetting = 25751,
33 | PrimalRend = 25753;
34 |
35 | public static class Buffs
36 | {
37 | public const ushort
38 | Berserk = 86,
39 | InnerRelease = 1177,
40 | NascentChaos = 1897,
41 | PrimalRendReady = 2624,
42 | SurgingTempest = 2677;
43 | }
44 |
45 | public static class Debuffs
46 | {
47 | public const ushort
48 | Placeholder = 0;
49 | }
50 |
51 | public static class Levels
52 | {
53 | public const byte
54 | Maim = 4,
55 | Berserk = 6,
56 | StormsPath = 26,
57 | ThrillOfBattle = 30,
58 | InnerBeast = 35,
59 | MythrilTempest = 40,
60 | StormsEye = 50,
61 | Infuriate = 50,
62 | FellCleave = 54,
63 | RawIntuition = 56,
64 | Equilibrium = 58,
65 | Decimate = 60,
66 | InnerRelease = 70,
67 | MythrilTempestTrait = 74,
68 | NascentFlash = 76,
69 | InnerChaos = 80,
70 | Bloodwhetting = 82,
71 | PrimalRend = 90;
72 | }
73 | }
74 |
75 | internal class WarriorStormsPathCombo : CustomCombo
76 | {
77 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WarriorStormsPathCombo;
78 |
79 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
80 | {
81 | if (actionID == WAR.StormsPath)
82 | {
83 | var gauge = GetJobGauge();
84 |
85 | if (IsEnabled(CustomComboPreset.WarriorStormsPathInnerReleaseFeature))
86 | {
87 | if (level >= WAR.Levels.InnerRelease && HasEffect(WAR.Buffs.InnerRelease))
88 | return OriginalHook(WAR.FellCleave);
89 | }
90 |
91 | if (comboTime > 0)
92 | {
93 | if (lastComboMove == WAR.Maim && level >= WAR.Levels.StormsPath)
94 | {
95 | if (IsEnabled(CustomComboPreset.WarriorStormsPathOvercapFeature))
96 | {
97 | if (level >= WAR.Levels.InnerBeast && gauge.BeastGauge > 80)
98 | // Fell Cleave
99 | return OriginalHook(WAR.InnerBeast);
100 | }
101 |
102 | #if DEBUG
103 | if (level >= WAR.Levels.StormsEye)
104 | {
105 | var surgingTempest = FindEffect(WAR.Buffs.SurgingTempest);
106 | if (surgingTempest is null)
107 | return WAR.StormsEye;
108 |
109 | // Medicated + Opener
110 | if (HasEffect(ADV.Buffs.Medicated) && surgingTempest.RemainingTime > 10)
111 | return WAR.StormsPath;
112 |
113 | var innerReleleaseCd = GetCooldown(WAR.InnerRelease).CooldownRemaining;
114 | var surgingTempestFromIR = Math.Max(0, 10 - innerReleleaseCd);
115 | if (surgingTempest.RemainingTime + 30 + surgingTempestFromIR < 60)
116 | return WAR.StormsEye;
117 | }
118 | #endif
119 |
120 | return WAR.StormsPath;
121 | }
122 |
123 | if (lastComboMove == WAR.HeavySwing && level >= WAR.Levels.Maim)
124 | {
125 | if (IsEnabled(CustomComboPreset.WarriorStormsPathOvercapFeature))
126 | {
127 | if (level >= WAR.Levels.InnerBeast && gauge.BeastGauge > 90)
128 | // Fell Cleave
129 | return OriginalHook(WAR.InnerBeast);
130 | }
131 |
132 | return WAR.Maim;
133 | }
134 | }
135 |
136 | return WAR.HeavySwing;
137 | }
138 |
139 | return actionID;
140 | }
141 | }
142 |
143 | internal class WarriorStormsEyeCombo : CustomCombo
144 | {
145 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WarriorStormsEyeCombo;
146 |
147 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
148 | {
149 | if (actionID == WAR.StormsEye)
150 | {
151 | if (comboTime > 0)
152 | {
153 | if (lastComboMove == WAR.Maim && level >= WAR.Levels.StormsEye)
154 | return WAR.StormsEye;
155 |
156 | if (lastComboMove == WAR.HeavySwing && level >= WAR.Levels.Maim)
157 | return WAR.Maim;
158 | }
159 |
160 | return WAR.HeavySwing;
161 | }
162 |
163 | return actionID;
164 | }
165 | }
166 |
167 | internal class WarriorMythrilTempestCombo : CustomCombo
168 | {
169 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WarriorMythrilTempestCombo;
170 |
171 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
172 | {
173 | if (actionID == WAR.MythrilTempest)
174 | {
175 | var gauge = GetJobGauge();
176 |
177 | if (IsEnabled(CustomComboPreset.WarriorMythrilTempestInnerReleaseFeature))
178 | {
179 | if (level >= WAR.Levels.InnerRelease && HasEffect(WAR.Buffs.InnerRelease))
180 | return OriginalHook(WAR.Decimate);
181 | }
182 |
183 | if (comboTime > 0)
184 | {
185 | if (lastComboMove == WAR.Overpower && level >= WAR.Levels.MythrilTempest)
186 | {
187 | if (IsEnabled(CustomComboPreset.WarriorMythrilTempestOvercapFeature))
188 | {
189 | if (level >= WAR.Levels.MythrilTempestTrait && gauge.BeastGauge > 80)
190 | return OriginalHook(WAR.Decimate);
191 | }
192 |
193 | return WAR.MythrilTempest;
194 | }
195 | }
196 |
197 | return WAR.Overpower;
198 | }
199 |
200 | return actionID;
201 | }
202 | }
203 |
204 | internal class WarriorFellCleaveDecimate : CustomCombo
205 | {
206 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WarAny;
207 |
208 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
209 | {
210 | if (actionID == WAR.InnerBeast || actionID == WAR.FellCleave || actionID == WAR.SteelCyclone || actionID == WAR.Decimate)
211 | {
212 | if (IsEnabled(CustomComboPreset.WarriorPrimalBeastFeature))
213 | {
214 | if (level >= WAR.Levels.PrimalRend && HasEffect(WAR.Buffs.PrimalRendReady))
215 | return WAR.PrimalRend;
216 | }
217 |
218 | if (IsEnabled(CustomComboPreset.WarriorInfuriateBeastFeature))
219 | {
220 | var gauge = GetJobGauge();
221 |
222 | if (level >= WAR.Levels.Infuriate && gauge.BeastGauge < 50 && !HasEffect(WAR.Buffs.InnerRelease))
223 | return WAR.Infuriate;
224 | }
225 | }
226 |
227 | return actionID;
228 | }
229 | }
230 |
231 | internal class WarriorBerserkInnerRelease : CustomCombo
232 | {
233 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WarriorPrimalReleaseFeature;
234 |
235 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
236 | {
237 | if (actionID == WAR.Berserk || actionID == WAR.InnerRelease)
238 | {
239 | if (level >= WAR.Levels.PrimalRend && HasEffect(WAR.Buffs.PrimalRendReady))
240 | return WAR.PrimalRend;
241 | }
242 |
243 | return actionID;
244 | }
245 | }
246 |
247 | internal class WarriorNascentFlashFeature : CustomCombo
248 | {
249 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WarriorNascentFlashSyncFeature;
250 |
251 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
252 | {
253 | if (actionID == WAR.NascentFlash)
254 | {
255 | if (level >= WAR.Levels.NascentFlash)
256 | return WAR.NascentFlash;
257 |
258 | if (level >= WAR.Levels.RawIntuition)
259 | return WAR.RawIntuition;
260 | }
261 |
262 | return actionID;
263 | }
264 | }
265 |
266 | internal class WarriorBloodwhetting : CustomCombo
267 | {
268 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.WarAny;
269 |
270 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
271 | {
272 | if (actionID == WAR.Bloodwhetting || actionID == WAR.RawIntuition)
273 | {
274 | if (IsEnabled(CustomComboPreset.WarriorHealthyBalancedDietFeature))
275 | {
276 | if (level >= WAR.Levels.Bloodwhetting)
277 | {
278 | if (IsOffCooldown(WAR.Bloodwhetting))
279 | return WAR.Bloodwhetting;
280 | }
281 | else if (level >= WAR.Levels.RawIntuition)
282 | {
283 | if (IsOffCooldown(WAR.RawIntuition))
284 | return WAR.RawIntuition;
285 | }
286 |
287 | if (level >= WAR.Levels.ThrillOfBattle && IsOffCooldown(WAR.ThrillOfBattle))
288 | return WAR.ThrillOfBattle;
289 |
290 | if (level >= WAR.Levels.Equilibrium && IsOffCooldown(WAR.Equilibrium))
291 | return WAR.Equilibrium;
292 | }
293 | }
294 |
295 | return actionID;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/DNC.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | using Dalamud.Game.ClientState.JobGauge.Types;
5 |
6 | namespace XIVComboExpandedPlugin.Combos;
7 |
8 | internal static class DNC
9 | {
10 | public const byte JobID = 38;
11 |
12 | public const uint
13 | // Single Target
14 | Cascade = 15989,
15 | Fountain = 15990,
16 | ReverseCascade = 15991,
17 | Fountainfall = 15992,
18 | // AoE
19 | Windmill = 15993,
20 | Bladeshower = 15994,
21 | RisingWindmill = 15995,
22 | Bloodshower = 15996,
23 | // Dancing
24 | StandardStep = 15997,
25 | TechnicalStep = 15998,
26 | Tillana = 25790,
27 | // Fans
28 | FanDance1 = 16007,
29 | FanDance2 = 16008,
30 | FanDance3 = 16009,
31 | FanDance4 = 25791,
32 | // Other
33 | SaberDance = 16005,
34 | EnAvant = 16010,
35 | Devilment = 16011,
36 | Flourish = 16013,
37 | Improvisation = 16014,
38 | StarfallDance = 25792;
39 |
40 | public static class Buffs
41 | {
42 | public const ushort
43 | FlourishingSymmetry = 3017,
44 | FlourishingFlow = 3018,
45 | FlourishingFinish = 2698,
46 | FlourishingStarfall = 2700,
47 | SilkenSymmetry = 2693,
48 | SilkenFlow = 2694,
49 | StandardStep = 1818,
50 | TechnicalStep = 1819,
51 | ThreefoldFanDance = 1820,
52 | FourfoldFanDance = 2699;
53 | }
54 |
55 | public static class Debuffs
56 | {
57 | public const ushort
58 | Placeholder = 0;
59 | }
60 |
61 | public static class Levels
62 | {
63 | public const byte
64 | Cascade = 1,
65 | Fountain = 2,
66 | Windmill = 15,
67 | StandardStep = 15,
68 | ReverseCascade = 20,
69 | Bladeshower = 25,
70 | RisingWindmill = 35,
71 | Fountainfall = 40,
72 | Bloodshower = 45,
73 | FanDance3 = 66,
74 | TechnicalStep = 70,
75 | Flourish = 72,
76 | Tillana = 82,
77 | FanDance4 = 86,
78 | StarfallDance = 90;
79 | }
80 | }
81 |
82 | internal class DancerDanceComboCompatibility : CustomCombo
83 | {
84 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DancerDanceComboCompatibility;
85 |
86 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
87 | {
88 | var actionIDs = Service.Configuration.DancerDanceCompatActionIDs;
89 |
90 | if (actionIDs.Contains(actionID))
91 | {
92 | var gauge = GetJobGauge();
93 |
94 | if (level >= DNC.Levels.StandardStep && gauge.IsDancing)
95 | {
96 | if (actionID == actionIDs[0] || (actionIDs[0] == 0 && actionID == DNC.Cascade))
97 | return OriginalHook(DNC.Cascade);
98 |
99 | if (actionID == actionIDs[1] || (actionIDs[1] == 0 && actionID == DNC.Flourish))
100 | return OriginalHook(DNC.Fountain);
101 |
102 | if (actionID == actionIDs[2] || (actionIDs[2] == 0 && actionID == DNC.FanDance1))
103 | return OriginalHook(DNC.ReverseCascade);
104 |
105 | if (actionID == actionIDs[3] || (actionIDs[3] == 0 && actionID == DNC.FanDance2))
106 | return OriginalHook(DNC.Fountainfall);
107 | }
108 | }
109 |
110 | return actionID;
111 | }
112 | }
113 |
114 | internal class DancerFanDance12 : CustomCombo
115 | {
116 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DncAny;
117 |
118 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
119 | {
120 | if (actionID == DNC.FanDance1 || actionID == DNC.FanDance2)
121 | {
122 | var gauge = GetJobGauge();
123 |
124 | if (IsEnabled(CustomComboPreset.DancerFanDance3Feature))
125 | {
126 | if (IsEnabled(CustomComboPreset.DancerFanDance4Feature))
127 | {
128 | if (gauge.Feathers == 4)
129 | {
130 | if (level >= DNC.Levels.FanDance3 && HasEffect(DNC.Buffs.ThreefoldFanDance))
131 | return DNC.FanDance3;
132 |
133 | return actionID;
134 | }
135 |
136 | if (level >= DNC.Levels.FanDance4 && HasEffect(DNC.Buffs.FourfoldFanDance))
137 | return DNC.FanDance4;
138 | }
139 |
140 | if (level >= DNC.Levels.FanDance3 && HasEffect(DNC.Buffs.ThreefoldFanDance))
141 | return DNC.FanDance3;
142 | }
143 | }
144 |
145 | return actionID;
146 | }
147 | }
148 |
149 | internal class DancerStandardStepTechnicalStep : CustomCombo
150 | {
151 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DancerDanceStepCombo;
152 |
153 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
154 | {
155 | if (actionID == DNC.StandardStep)
156 | {
157 | var gauge = GetJobGauge();
158 |
159 | if (level >= DNC.Levels.StandardStep && gauge.IsDancing && HasEffect(DNC.Buffs.StandardStep))
160 | {
161 | if (gauge.CompletedSteps < 2)
162 | return gauge.NextStep;
163 |
164 | return OriginalHook(DNC.StandardStep);
165 | }
166 |
167 | return DNC.StandardStep;
168 | }
169 |
170 | if (actionID == DNC.TechnicalStep)
171 | {
172 | var gauge = GetJobGauge();
173 |
174 | if (level >= DNC.Levels.TechnicalStep && gauge.IsDancing && HasEffect(DNC.Buffs.TechnicalStep))
175 | {
176 | if (gauge.CompletedSteps < 4)
177 | return gauge.NextStep;
178 | }
179 |
180 | // Tillana
181 | return OriginalHook(DNC.TechnicalStep);
182 | }
183 |
184 | return actionID;
185 | }
186 | }
187 |
188 | internal class DancerFlourish : CustomCombo
189 | {
190 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DncAny;
191 |
192 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
193 | {
194 | if (actionID == DNC.Flourish)
195 | {
196 | if (IsEnabled(CustomComboPreset.DancerFlourishFan3Feature))
197 | {
198 | if (level >= DNC.Levels.FanDance3 && HasEffect(DNC.Buffs.ThreefoldFanDance))
199 | return DNC.FanDance3;
200 | }
201 |
202 | if (IsEnabled(CustomComboPreset.DancerFlourishFan4Feature))
203 | {
204 | if (level >= DNC.Levels.FanDance4 && HasEffect(DNC.Buffs.FourfoldFanDance))
205 | return DNC.FanDance4;
206 | }
207 | }
208 |
209 | return actionID;
210 | }
211 | }
212 |
213 | internal class DancerCascadeFountain : CustomCombo
214 | {
215 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DncAny;
216 |
217 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
218 | {
219 | if (actionID == DNC.Cascade)
220 | {
221 | if (IsEnabled(CustomComboPreset.DancerSingleTargetMultibutton))
222 | {
223 | if (level >= DNC.Levels.Fountainfall && (HasEffect(DNC.Buffs.FlourishingFlow) || HasEffect(DNC.Buffs.SilkenFlow)))
224 | return DNC.Fountainfall;
225 |
226 | if (level >= DNC.Levels.ReverseCascade && (HasEffect(DNC.Buffs.FlourishingSymmetry) || HasEffect(DNC.Buffs.SilkenSymmetry)))
227 | return DNC.ReverseCascade;
228 |
229 | if (lastComboMove == DNC.Cascade && level >= DNC.Levels.Fountain)
230 | return DNC.Fountain;
231 | }
232 |
233 | if (IsEnabled(CustomComboPreset.DancerSingleTargetProcs))
234 | {
235 | if (level >= DNC.Levels.ReverseCascade && (HasEffect(DNC.Buffs.FlourishingSymmetry) || HasEffect(DNC.Buffs.SilkenSymmetry)))
236 | return DNC.ReverseCascade;
237 | }
238 | }
239 |
240 | if (actionID == DNC.Fountain)
241 | {
242 | if (IsEnabled(CustomComboPreset.DancerSingleTargetProcs))
243 | {
244 | if (level >= DNC.Levels.Fountainfall && (HasEffect(DNC.Buffs.FlourishingFlow) || HasEffect(DNC.Buffs.SilkenFlow)))
245 | return DNC.Fountainfall;
246 | }
247 | }
248 |
249 | return actionID;
250 | }
251 | }
252 |
253 | internal class DancerWindmillBladeshower : CustomCombo
254 | {
255 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DncAny;
256 |
257 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
258 | {
259 | if (actionID == DNC.Windmill)
260 | {
261 | if (IsEnabled(CustomComboPreset.DancerAoeMultibutton))
262 | {
263 | if (level >= DNC.Levels.Bloodshower && (HasEffect(DNC.Buffs.FlourishingFlow) || HasEffect(DNC.Buffs.SilkenFlow)))
264 | return DNC.Bloodshower;
265 |
266 | if (level >= DNC.Levels.RisingWindmill && (HasEffect(DNC.Buffs.FlourishingSymmetry) || HasEffect(DNC.Buffs.SilkenSymmetry)))
267 | return DNC.RisingWindmill;
268 |
269 | if (lastComboMove == DNC.Windmill && level >= DNC.Levels.Bladeshower)
270 | return DNC.Bladeshower;
271 | }
272 |
273 | if (IsEnabled(CustomComboPreset.DancerAoeProcs))
274 | {
275 | if (level >= DNC.Levels.RisingWindmill && (HasEffect(DNC.Buffs.FlourishingSymmetry) || HasEffect(DNC.Buffs.SilkenSymmetry)))
276 | return DNC.RisingWindmill;
277 | }
278 | }
279 |
280 | if (actionID == DNC.Bladeshower)
281 | {
282 | if (IsEnabled(CustomComboPreset.DancerAoeProcs))
283 | {
284 | if (level >= DNC.Levels.Bloodshower && (HasEffect(DNC.Buffs.FlourishingFlow) || HasEffect(DNC.Buffs.SilkenFlow)))
285 | return DNC.Bloodshower;
286 | }
287 | }
288 |
289 | return actionID;
290 | }
291 | }
292 |
293 | internal class DancerDevilment : CustomCombo
294 | {
295 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DancerDevilmentFeature;
296 |
297 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
298 | {
299 | if (actionID == DNC.Devilment)
300 | {
301 | if (level >= DNC.Levels.StarfallDance && HasEffect(DNC.Buffs.FlourishingStarfall))
302 | return DNC.StarfallDance;
303 | }
304 |
305 | return actionID;
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/NIN.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class NIN
6 | {
7 | public const byte ClassID = 29;
8 | public const byte JobID = 30;
9 |
10 | public const uint
11 | SpinningEdge = 2240,
12 | GustSlash = 2242,
13 | Hide = 2245,
14 | Assassinate = 8814,
15 | Mug = 2248,
16 | DeathBlossom = 2254,
17 | AeolianEdge = 2255,
18 | TrickAttack = 2258,
19 | Ninjutsu = 2260,
20 | Chi = 2261,
21 | JinNormal = 2263,
22 | Kassatsu = 2264,
23 | ArmorCrush = 3563,
24 | DreamWithinADream = 3566,
25 | TenChiJin = 7403,
26 | HakkeMujinsatsu = 16488,
27 | Meisui = 16489,
28 | Jin = 18807,
29 | Bunshin = 16493,
30 | Huraijin = 25876,
31 | PhantomKamaitachi = 25774,
32 | ForkedRaiju = 25777,
33 | FleetingRaiju = 25778;
34 |
35 | public static class Buffs
36 | {
37 | public const ushort
38 | Mudra = 496,
39 | Kassatsu = 497,
40 | Suiton = 507,
41 | Hidden = 614,
42 | Bunshin = 1954,
43 | RaijuReady = 2690;
44 | }
45 |
46 | public static class Debuffs
47 | {
48 | public const ushort
49 | Placeholder = 0;
50 | }
51 |
52 | public static class Levels
53 | {
54 | public const byte
55 | GustSlash = 4,
56 | Hide = 10,
57 | Mug = 15,
58 | AeolianEdge = 26,
59 | Ninjitsu = 30,
60 | Suiton = 45,
61 | HakkeMujinsatsu = 52,
62 | ArmorCrush = 54,
63 | Huraijin = 60,
64 | TenChiJin = 70,
65 | Meisui = 72,
66 | EnhancedKassatsu = 76,
67 | Bunshin = 80,
68 | PhantomKamaitachi = 82,
69 | Raiju = 90;
70 | }
71 | }
72 |
73 | internal class NinjaAeolianEdge : CustomCombo
74 | {
75 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinAny;
76 |
77 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
78 | {
79 | if (actionID == NIN.AeolianEdge)
80 | {
81 | var gauge = GetJobGauge();
82 |
83 | if (IsEnabled(CustomComboPreset.NinjaAeolianNinjutsuFeature))
84 | {
85 | if (level >= NIN.Levels.Ninjitsu && HasEffect(NIN.Buffs.Mudra))
86 | return OriginalHook(NIN.Ninjutsu);
87 | }
88 |
89 | if (IsEnabled(CustomComboPreset.NinjaAeolianEdgeRaijuFeature))
90 | {
91 | if (level >= NIN.Levels.Raiju && HasEffect(NIN.Buffs.RaijuReady))
92 | return NIN.FleetingRaiju;
93 | }
94 |
95 | if (IsEnabled(CustomComboPreset.NinjaAeolianEdgeHutonFeature))
96 | {
97 | if (level >= NIN.Levels.Huraijin && gauge.HutonTimer == 0)
98 | return NIN.Huraijin;
99 |
100 | if (comboTime > 0)
101 | {
102 | if (lastComboMove == NIN.GustSlash && level >= NIN.Levels.ArmorCrush && gauge.HutonTimer <= 30_000)
103 | return NIN.ArmorCrush;
104 | }
105 | }
106 |
107 | if (IsEnabled(CustomComboPreset.NinjaAeolianEdgeCombo))
108 | {
109 | if (comboTime > 0)
110 | {
111 | if (lastComboMove == NIN.GustSlash && level >= NIN.Levels.AeolianEdge)
112 | return NIN.AeolianEdge;
113 |
114 | if (lastComboMove == NIN.SpinningEdge && level >= NIN.Levels.GustSlash)
115 | return NIN.GustSlash;
116 | }
117 |
118 | return NIN.SpinningEdge;
119 | }
120 | }
121 |
122 | return actionID;
123 | }
124 | }
125 |
126 | internal class NinjaArmorCrush : CustomCombo
127 | {
128 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinAny;
129 |
130 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
131 | {
132 | if (actionID == NIN.ArmorCrush)
133 | {
134 | if (IsEnabled(CustomComboPreset.NinjaArmorCrushRaijuFeature))
135 | {
136 | if (level >= NIN.Levels.Raiju && HasEffect(NIN.Buffs.RaijuReady))
137 | return NIN.ForkedRaiju;
138 | }
139 |
140 | if (IsEnabled(CustomComboPreset.NinjaArmorCrushNinjutsuFeature))
141 | {
142 | if (level >= NIN.Levels.Ninjitsu && HasEffect(NIN.Buffs.Mudra))
143 | return OriginalHook(NIN.Ninjutsu);
144 | }
145 |
146 | if (IsEnabled(CustomComboPreset.NinjaArmorCrushCombo))
147 | {
148 | if (comboTime > 0)
149 | {
150 | if (lastComboMove == NIN.GustSlash && level >= NIN.Levels.ArmorCrush)
151 | return NIN.ArmorCrush;
152 |
153 | if (lastComboMove == NIN.SpinningEdge && level >= NIN.Levels.GustSlash)
154 | return NIN.GustSlash;
155 | }
156 |
157 | return NIN.SpinningEdge;
158 | }
159 | }
160 |
161 | return actionID;
162 | }
163 | }
164 |
165 | internal class NinjaHuraijin : CustomCombo
166 | {
167 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinAny;
168 |
169 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
170 | {
171 | if (actionID == NIN.Huraijin)
172 | {
173 | if (level >= NIN.Levels.Raiju && HasEffect(NIN.Buffs.RaijuReady))
174 | {
175 | if (IsEnabled(CustomComboPreset.NinjaHuraijinForkedRaijuFeature))
176 | return NIN.ForkedRaiju;
177 |
178 | if (IsEnabled(CustomComboPreset.NinjaHuraijinFleetingRaijuFeature))
179 | return NIN.FleetingRaiju;
180 | }
181 |
182 | if (IsEnabled(CustomComboPreset.NinjaHuraijinNinjutsuFeature))
183 | {
184 | if (level >= NIN.Levels.Ninjitsu && HasEffect(NIN.Buffs.Mudra))
185 | return OriginalHook(NIN.Ninjutsu);
186 | }
187 |
188 | if (IsEnabled(CustomComboPreset.NinjaHuraijinArmorCrushCombo))
189 | {
190 | var gauge = GetJobGauge();
191 |
192 | if (comboTime > 0 && gauge.HutonTimer > 0)
193 | {
194 | if (lastComboMove == NIN.GustSlash && level >= NIN.Levels.ArmorCrush)
195 | return NIN.ArmorCrush;
196 | }
197 | }
198 | }
199 |
200 | return actionID;
201 | }
202 | }
203 |
204 | internal class NinjaHakkeMujinsatsu : CustomCombo
205 | {
206 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinAny;
207 |
208 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
209 | {
210 | if (actionID == NIN.HakkeMujinsatsu)
211 | {
212 | if (IsEnabled(CustomComboPreset.NinjaHakkeMujinsatsuNinjutsuFeature))
213 | {
214 | if (level >= NIN.Levels.Ninjitsu && HasEffect(NIN.Buffs.Mudra))
215 | return OriginalHook(NIN.Ninjutsu);
216 | }
217 |
218 | if (IsEnabled(CustomComboPreset.NinjaHakkeMujinsatsuCombo))
219 | {
220 | if (comboTime > 0)
221 | {
222 | if (lastComboMove == NIN.DeathBlossom && level >= NIN.Levels.HakkeMujinsatsu)
223 | return NIN.HakkeMujinsatsu;
224 | }
225 |
226 | return NIN.DeathBlossom;
227 | }
228 | }
229 |
230 | return actionID;
231 | }
232 | }
233 |
234 | internal class NinjaKassatsu : CustomCombo
235 | {
236 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinjaKassatsuTrickFeature;
237 |
238 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
239 | {
240 | if (actionID == NIN.Kassatsu)
241 | {
242 | if ((level >= NIN.Levels.Hide && HasEffect(NIN.Buffs.Hidden)) ||
243 | (level >= NIN.Levels.Suiton && HasEffect(NIN.Buffs.Suiton)))
244 | return NIN.TrickAttack;
245 | }
246 |
247 | return actionID;
248 | }
249 | }
250 |
251 | internal class NinjaHide : CustomCombo
252 | {
253 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinAny;
254 |
255 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
256 | {
257 | if (actionID == NIN.Hide)
258 | {
259 | if (IsEnabled(CustomComboPreset.NinjaHideNinjutsuFeature))
260 | {
261 | if (level >= NIN.Levels.Ninjitsu && HasEffect(NIN.Buffs.Mudra))
262 | return OriginalHook(NIN.Ninjutsu);
263 | }
264 |
265 | if (IsEnabled(CustomComboPreset.NinjaHideMugFeature))
266 | {
267 | if (level >= NIN.Levels.Mug && InCombat())
268 | return NIN.Mug;
269 | }
270 | }
271 |
272 | return actionID;
273 | }
274 | }
275 |
276 | internal class NinjaChi : CustomCombo
277 | {
278 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinjaKassatsuChiJinFeature;
279 |
280 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
281 | {
282 | if (actionID == NIN.Chi)
283 | {
284 | if (level >= NIN.Levels.EnhancedKassatsu && HasEffect(NIN.Buffs.Kassatsu))
285 | return NIN.Jin;
286 | }
287 |
288 | return actionID;
289 | }
290 | }
291 |
292 | internal class NinjaTenChiJin : CustomCombo
293 | {
294 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinjaTCJMeisuiFeature;
295 |
296 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
297 | {
298 | if (actionID == NIN.TenChiJin)
299 | {
300 | if (level >= NIN.Levels.Meisui && HasEffect(NIN.Buffs.Suiton))
301 | return NIN.Meisui;
302 | }
303 |
304 | return actionID;
305 | }
306 | }
307 |
308 | internal class NinjaNinjitsu : CustomCombo
309 | {
310 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.NinAny;
311 |
312 | protected override uint Invoke(uint actionID, uint lastComboActionID, float comboTime, byte level)
313 | {
314 | if (actionID == NIN.Ninjutsu)
315 | {
316 | if (level >= NIN.Levels.Raiju && HasEffect(NIN.Buffs.RaijuReady) && !HasEffect(NIN.Buffs.Mudra))
317 | {
318 | if (IsEnabled(CustomComboPreset.NinjaNinjitsuForkedRaijuFeature))
319 | return NIN.ForkedRaiju;
320 |
321 | if (IsEnabled(CustomComboPreset.NinjaNinjitsuFleetingRaijuFeature))
322 | return NIN.FleetingRaiju;
323 | }
324 | }
325 |
326 | return actionID;
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/DRG.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Types;
2 |
3 | namespace XIVComboExpandedPlugin.Combos;
4 |
5 | internal static class DRG
6 | {
7 | public const byte ClassID = 4;
8 | public const byte JobID = 22;
9 |
10 | public const uint
11 | // Single Target
12 | TrueThrust = 75,
13 | VorpalThrust = 78,
14 | Disembowel = 87,
15 | FullThrust = 84,
16 | ChaosThrust = 88,
17 | HeavensThrust = 25771,
18 | ChaoticSpring = 25772,
19 | WheelingThrust = 3556,
20 | FangAndClaw = 3554,
21 | RaidenThrust = 16479,
22 | // AoE
23 | DoomSpike = 86,
24 | SonicThrust = 7397,
25 | CoerthanTorment = 16477,
26 | DraconianFury = 25770,
27 | // Combined
28 | Geirskogul = 3555,
29 | Nastrond = 7400,
30 | // Jumps
31 | Jump = 92,
32 | SpineshatterDive = 95,
33 | DragonfireDive = 96,
34 | HighJump = 16478,
35 | MirageDive = 7399,
36 | // Dragon
37 | Stardiver = 16480,
38 | WyrmwindThrust = 25773,
39 | // Buff abilities
40 | LanceCharge = 85,
41 | DragonSight = 7398,
42 | BattleLitany = 3557;
43 |
44 | public static class Buffs
45 | {
46 | public const ushort
47 | SharperFangAndClaw = 802,
48 | EnhancedWheelingThrust = 803,
49 | DiveReady = 1243;
50 | }
51 |
52 | public static class Debuffs
53 | {
54 | public const ushort
55 | Placeholder = 0;
56 | }
57 |
58 | public static class Levels
59 | {
60 | public const byte
61 | VorpalThrust = 4,
62 | Disembowel = 18,
63 | FullThrust = 26,
64 | SpineshatterDive = 45,
65 | DragonfireDive = 50,
66 | ChaosThrust = 50,
67 | BattleLitany = 52,
68 | HeavensThrust = 86,
69 | ChaoticSpring = 86,
70 | FangAndClaw = 56,
71 | WheelingThrust = 58,
72 | Geirskogul = 60,
73 | SonicThrust = 62,
74 | DragonSight = 66,
75 | MirageDive = 68,
76 | LifeOfTheDragon = 70,
77 | CoerthanTorment = 72,
78 | HighJump = 74,
79 | RaidenThrust = 76,
80 | Stardiver = 80,
81 | WyrmwindThrust = 90;
82 | }
83 | }
84 |
85 | internal class DragoonJump : CustomCombo
86 | {
87 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DragoonJumpFeature;
88 |
89 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
90 | {
91 | if (actionID == DRG.Jump || actionID == DRG.HighJump)
92 | {
93 | if (level >= DRG.Levels.MirageDive && HasEffect(DRG.Buffs.DiveReady))
94 | return DRG.MirageDive;
95 | }
96 |
97 | return actionID;
98 | }
99 | }
100 |
101 | internal class DragoonCoerthanTorment : CustomCombo
102 | {
103 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrgAny;
104 |
105 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
106 | {
107 | if (actionID == DRG.CoerthanTorment)
108 | {
109 | var gauge = GetJobGauge();
110 |
111 | if (IsEnabled(CustomComboPreset.DragoonCoerthanWyrmwindFeature))
112 | {
113 | if (gauge.FirstmindsFocusCount == 2)
114 | return DRG.WyrmwindThrust;
115 | }
116 |
117 | if (IsEnabled(CustomComboPreset.DragoonCoerthanTormentCombo))
118 | {
119 | if (comboTime > 0)
120 | {
121 | if (lastComboMove == DRG.SonicThrust && level >= DRG.Levels.CoerthanTorment)
122 | return DRG.CoerthanTorment;
123 |
124 | if ((lastComboMove == DRG.DoomSpike || lastComboMove == DRG.DraconianFury) && level >= DRG.Levels.SonicThrust)
125 | return DRG.SonicThrust;
126 | }
127 |
128 | // Draconian Fury
129 | return OriginalHook(DRG.DoomSpike);
130 | }
131 | }
132 |
133 | return actionID;
134 | }
135 | }
136 |
137 | internal class DragoonChaosThrust : CustomCombo
138 | {
139 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrgAny;
140 |
141 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
142 | {
143 | if (actionID == DRG.ChaosThrust || actionID == DRG.ChaoticSpring)
144 | {
145 | if (IsEnabled(CustomComboPreset.DragoonFangThrustFeature))
146 | {
147 | if (level >= DRG.Levels.FangAndClaw && (HasEffect(DRG.Buffs.SharperFangAndClaw) || HasEffect(DRG.Buffs.EnhancedWheelingThrust)))
148 | return DRG.WheelingThrust;
149 | }
150 |
151 | if (IsEnabled(CustomComboPreset.DragoonChaosThrustCombo))
152 | {
153 | if (level >= DRG.Levels.FangAndClaw && HasEffect(DRG.Buffs.SharperFangAndClaw))
154 | return DRG.FangAndClaw;
155 |
156 | if (level >= DRG.Levels.WheelingThrust && HasEffect(DRG.Buffs.EnhancedWheelingThrust))
157 | return DRG.WheelingThrust;
158 |
159 | if (comboTime > 0)
160 | {
161 | if (lastComboMove == DRG.Disembowel && level >= DRG.Levels.ChaosThrust)
162 | // ChaoticSpring
163 | return OriginalHook(DRG.ChaosThrust);
164 |
165 | if ((lastComboMove == DRG.TrueThrust || lastComboMove == DRG.RaidenThrust) && level >= DRG.Levels.Disembowel)
166 | return DRG.Disembowel;
167 | }
168 |
169 | if (IsEnabled(CustomComboPreset.DragoonChaosThrustComboOption))
170 | return DRG.Disembowel;
171 |
172 | // Vorpal Thrust
173 | return OriginalHook(DRG.TrueThrust);
174 | }
175 | }
176 |
177 | return actionID;
178 | }
179 | }
180 |
181 | internal class DragoonFullThrust : CustomCombo
182 | {
183 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrgAny;
184 |
185 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
186 | {
187 | if (actionID == DRG.FullThrust || actionID == DRG.HeavensThrust)
188 | {
189 | if (IsEnabled(CustomComboPreset.DragoonFangThrustFeature))
190 | {
191 | if (level >= DRG.Levels.FangAndClaw && (HasEffect(DRG.Buffs.SharperFangAndClaw) || HasEffect(DRG.Buffs.EnhancedWheelingThrust)))
192 | return DRG.FangAndClaw;
193 | }
194 |
195 | if (IsEnabled(CustomComboPreset.DragoonFullThrustCombo))
196 | {
197 | if (level >= DRG.Levels.WheelingThrust && HasEffect(DRG.Buffs.EnhancedWheelingThrust))
198 | return DRG.WheelingThrust;
199 |
200 | if (level >= DRG.Levels.FangAndClaw && HasEffect(DRG.Buffs.SharperFangAndClaw))
201 | return DRG.FangAndClaw;
202 |
203 | if (comboTime > 0)
204 | {
205 | if (lastComboMove == DRG.VorpalThrust && level >= DRG.Levels.FullThrust)
206 | // Heavens' Thrust
207 | return OriginalHook(DRG.FullThrust);
208 |
209 | if ((lastComboMove == DRG.TrueThrust || lastComboMove == DRG.RaidenThrust) && level >= DRG.Levels.VorpalThrust)
210 | return DRG.VorpalThrust;
211 | }
212 |
213 | if (IsEnabled(CustomComboPreset.DragoonFullThrustComboOption))
214 | return DRG.VorpalThrust;
215 |
216 | // Vorpal Thrust
217 | return OriginalHook(DRG.TrueThrust);
218 | }
219 | }
220 |
221 | return actionID;
222 | }
223 | }
224 |
225 | internal class DragoonStardiver : CustomCombo
226 | {
227 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DrgAny;
228 |
229 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
230 | {
231 | if (actionID == DRG.Stardiver)
232 | {
233 | var gauge = GetJobGauge();
234 |
235 | if (IsEnabled(CustomComboPreset.DragoonStardiverNastrondFeature))
236 | {
237 | if (level >= DRG.Levels.Geirskogul && (!gauge.IsLOTDActive || IsOffCooldown(DRG.Nastrond) || IsOnCooldown(DRG.Stardiver)))
238 | // Nastrond
239 | return OriginalHook(DRG.Geirskogul);
240 | }
241 |
242 | if (IsEnabled(CustomComboPreset.DragoonStardiverDragonfireDiveFeature))
243 | {
244 | if (level < DRG.Levels.Stardiver || !gauge.IsLOTDActive || IsOnCooldown(DRG.Stardiver) || (IsOffCooldown(DRG.DragonfireDive) && gauge.LOTDTimer > 7.5))
245 | return DRG.DragonfireDive;
246 | }
247 | }
248 |
249 | return actionID;
250 | }
251 | }
252 |
253 | internal class DragoonDives : CustomCombo
254 | {
255 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DragoonDiveFeature;
256 |
257 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
258 | {
259 | if (actionID == DRG.SpineshatterDive || actionID == DRG.DragonfireDive || actionID == DRG.Stardiver)
260 | {
261 | if (level >= DRG.Levels.Stardiver)
262 | {
263 | var gauge = GetJobGauge();
264 |
265 | if (gauge.IsLOTDActive)
266 | return CalcBestAction(actionID, DRG.SpineshatterDive, DRG.DragonfireDive, DRG.Stardiver);
267 |
268 | return CalcBestAction(actionID, DRG.SpineshatterDive, DRG.DragonfireDive);
269 | }
270 |
271 | if (level >= DRG.Levels.DragonfireDive)
272 | return CalcBestAction(actionID, DRG.SpineshatterDive, DRG.DragonfireDive);
273 |
274 | if (level >= DRG.Levels.SpineshatterDive)
275 | return DRG.SpineshatterDive;
276 | }
277 |
278 | return actionID;
279 | }
280 | }
281 |
282 | internal class DragoonGierskogul : CustomCombo
283 | {
284 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DragoonGeirskogulWyrmwindFeature;
285 |
286 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
287 | {
288 | if (actionID == DRG.Geirskogul)
289 | {
290 | if (level >= DRG.Levels.WyrmwindThrust)
291 | {
292 | var gauge = GetJobGauge();
293 |
294 | if (gauge.FirstmindsFocusCount == 2)
295 | {
296 | var action = gauge.IsLOTDActive
297 | ? DRG.Nastrond
298 | : DRG.Geirskogul;
299 |
300 | if (IsOnCooldown(action))
301 | return DRG.WyrmwindThrust;
302 | }
303 | }
304 | }
305 |
306 | return actionID;
307 | }
308 | }
309 |
310 | internal class DragoonLanceCharge : CustomCombo
311 | {
312 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.DragoonLanceChargeFeature;
313 |
314 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
315 | {
316 | if (actionID == DRG.LanceCharge)
317 | {
318 | if (!IsOnCooldown(DRG.LanceCharge))
319 | return DRG.LanceCharge;
320 |
321 | if (level >= DRG.Levels.DragonSight && !IsOnCooldown(DRG.DragonSight))
322 | return DRG.DragonSight;
323 |
324 | if (level >= DRG.Levels.BattleLitany && !IsOnCooldown(DRG.BattleLitany))
325 | return DRG.BattleLitany;
326 | }
327 |
328 | return actionID;
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/SAM.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.JobGauge.Enums;
2 | using Dalamud.Game.ClientState.JobGauge.Types;
3 |
4 | namespace XIVComboExpandedPlugin.Combos;
5 |
6 | internal static class SAM
7 | {
8 | public const byte JobID = 34;
9 |
10 | public const uint
11 | // Single target
12 | Hakaze = 7477,
13 | Jinpu = 7478,
14 | Shifu = 7479,
15 | Yukikaze = 7480,
16 | Gekko = 7481,
17 | Kasha = 7482,
18 | // AoE
19 | Fuga = 7483,
20 | Mangetsu = 7484,
21 | Oka = 7485,
22 | Fuko = 25780,
23 | // Iaijutsu and Tsubame
24 | Iaijutsu = 7867,
25 | TsubameGaeshi = 16483,
26 | KaeshiHiganbana = 16484,
27 | Shoha = 16487,
28 | // Misc
29 | HissatsuShinten = 7490,
30 | HissatsuKyuten = 7491,
31 | HissatsuSenei = 16481,
32 | HissatsuGuren = 7496,
33 | Ikishoten = 16482,
34 | Shoha2 = 25779,
35 | OgiNamikiri = 25781,
36 | KaeshiNamikiri = 25782;
37 |
38 | public static class Buffs
39 | {
40 | public const ushort
41 | MeikyoShisui = 1233,
42 | EyesOpen = 1252,
43 | Jinpu = 1298,
44 | Shifu = 1299,
45 | OgiNamikiriReady = 2959;
46 | }
47 |
48 | public static class Debuffs
49 | {
50 | public const ushort
51 | Placeholder = 0;
52 | }
53 |
54 | public static class Levels
55 | {
56 | public const byte
57 | Jinpu = 4,
58 | Shifu = 18,
59 | Gekko = 30,
60 | Mangetsu = 35,
61 | Kasha = 40,
62 | Oka = 45,
63 | Yukikaze = 50,
64 | MeikyoShisui = 50,
65 | HissatsuKyuten = 64,
66 | HissatsuGuren = 70,
67 | HissatsuSenei = 72,
68 | TsubameGaeshi = 76,
69 | Shoha = 80,
70 | Shoha2 = 82,
71 | Hyosetsu = 86,
72 | Fuko = 86,
73 | OgiNamikiri = 90;
74 | }
75 | }
76 |
77 | internal class SamuraiYukikaze : CustomCombo
78 | {
79 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamuraiYukikazeCombo;
80 |
81 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
82 | {
83 | if (actionID == SAM.Yukikaze)
84 | {
85 | if (level >= SAM.Levels.MeikyoShisui && HasEffect(SAM.Buffs.MeikyoShisui))
86 | return SAM.Yukikaze;
87 |
88 | if (comboTime > 0)
89 | {
90 | if (lastComboMove == SAM.Hakaze && level >= SAM.Levels.Yukikaze)
91 | return SAM.Yukikaze;
92 | }
93 |
94 | return SAM.Hakaze;
95 | }
96 |
97 | return actionID;
98 | }
99 | }
100 |
101 | internal class SamuraiGekko : CustomCombo
102 | {
103 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamuraiGekkoCombo;
104 |
105 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
106 | {
107 | if (actionID == SAM.Gekko)
108 | {
109 | if (level >= SAM.Levels.MeikyoShisui && HasEffect(SAM.Buffs.MeikyoShisui))
110 | return SAM.Gekko;
111 |
112 | if (comboTime > 0)
113 | {
114 | if (lastComboMove == SAM.Jinpu && level >= SAM.Levels.Gekko)
115 | return SAM.Gekko;
116 |
117 | if (lastComboMove == SAM.Hakaze && level >= SAM.Levels.Jinpu)
118 | return SAM.Jinpu;
119 | }
120 |
121 | if (IsEnabled(CustomComboPreset.SamuraiGekkoOption))
122 | return SAM.Jinpu;
123 |
124 | return SAM.Hakaze;
125 | }
126 |
127 | return actionID;
128 | }
129 | }
130 |
131 | internal class SamuraiKasha : CustomCombo
132 | {
133 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamuraiKashaCombo;
134 |
135 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
136 | {
137 | if (actionID == SAM.Kasha)
138 | {
139 | if (level >= SAM.Levels.MeikyoShisui && HasEffect(SAM.Buffs.MeikyoShisui))
140 | return SAM.Kasha;
141 |
142 | if (comboTime > 0)
143 | {
144 | if (lastComboMove == SAM.Shifu && level >= SAM.Levels.Kasha)
145 | return SAM.Kasha;
146 |
147 | if (lastComboMove == SAM.Hakaze && level >= SAM.Levels.Shifu)
148 | return SAM.Shifu;
149 | }
150 |
151 | if (IsEnabled(CustomComboPreset.SamuraiKashaOption))
152 | return SAM.Shifu;
153 |
154 | return SAM.Hakaze;
155 | }
156 |
157 | return actionID;
158 | }
159 | }
160 |
161 | internal class SamuraiMangetsu : CustomCombo
162 | {
163 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamuraiMangetsuCombo;
164 |
165 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
166 | {
167 | if (actionID == SAM.Mangetsu)
168 | {
169 | if (level >= SAM.Levels.MeikyoShisui && HasEffect(SAM.Buffs.MeikyoShisui))
170 | return SAM.Mangetsu;
171 |
172 | if (comboTime > 0)
173 | {
174 | if ((lastComboMove == SAM.Fuga || lastComboMove == SAM.Fuko) && level >= SAM.Levels.Mangetsu)
175 | return SAM.Mangetsu;
176 | }
177 |
178 | // Fuko
179 | return OriginalHook(SAM.Fuga);
180 | }
181 |
182 | return actionID;
183 | }
184 | }
185 |
186 | internal class SamuraiOka : CustomCombo
187 | {
188 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamuraiOkaCombo;
189 |
190 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
191 | {
192 | if (actionID == SAM.Oka)
193 | {
194 | if (level >= SAM.Levels.MeikyoShisui && HasEffect(SAM.Buffs.MeikyoShisui))
195 | return SAM.Oka;
196 |
197 | if (comboTime > 0)
198 | {
199 | if ((lastComboMove == SAM.Fuga || lastComboMove == SAM.Fuko) && level >= SAM.Levels.Oka)
200 | return SAM.Oka;
201 | }
202 |
203 | // Fuko
204 | return OriginalHook(SAM.Fuga);
205 | }
206 |
207 | return actionID;
208 | }
209 | }
210 |
211 | internal class SamuraiTsubame : CustomCombo
212 | {
213 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamAny;
214 |
215 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
216 | {
217 | if (actionID == SAM.TsubameGaeshi)
218 | {
219 | var gauge = GetJobGauge();
220 |
221 | if (IsEnabled(CustomComboPreset.SamuraiTsubameGaeshiShohaFeature))
222 | {
223 | if (level >= SAM.Levels.Shoha && gauge.MeditationStacks >= 3)
224 | return SAM.Shoha;
225 | }
226 |
227 | if (IsEnabled(CustomComboPreset.SamuraiTsubameGaeshiIaijutsuFeature))
228 | {
229 | if (level >= SAM.Levels.TsubameGaeshi && gauge.Sen == Sen.NONE)
230 | return OriginalHook(SAM.TsubameGaeshi);
231 |
232 | return OriginalHook(SAM.Iaijutsu);
233 | }
234 | }
235 |
236 | return actionID;
237 | }
238 | }
239 |
240 | internal class SamuraiIaijutsu : CustomCombo
241 | {
242 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamAny;
243 |
244 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
245 | {
246 | if (actionID == SAM.Iaijutsu)
247 | {
248 | var gauge = GetJobGauge();
249 |
250 | if (IsEnabled(CustomComboPreset.SamuraiIaijutsuShohaFeature))
251 | {
252 | if (level >= SAM.Levels.Shoha && gauge.MeditationStacks >= 3)
253 | return SAM.Shoha;
254 | }
255 |
256 | if (IsEnabled(CustomComboPreset.SamuraiIaijutsuTsubameGaeshiFeature))
257 | {
258 | if (level >= SAM.Levels.TsubameGaeshi && gauge.Sen == Sen.NONE)
259 | return OriginalHook(SAM.TsubameGaeshi);
260 |
261 | return OriginalHook(SAM.Iaijutsu);
262 | }
263 | }
264 |
265 | return actionID;
266 | }
267 | }
268 |
269 | internal class SamuraiShinten : CustomCombo
270 | {
271 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamAny;
272 |
273 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
274 | {
275 | if (actionID == SAM.HissatsuShinten)
276 | {
277 | var gauge = GetJobGauge();
278 |
279 | if (IsEnabled(CustomComboPreset.SamuraiShintenShohaFeature))
280 | {
281 | if (level >= SAM.Levels.Shoha && gauge.MeditationStacks >= 3)
282 | return SAM.Shoha;
283 | }
284 |
285 | if (IsEnabled(CustomComboPreset.SamuraiShintenSeneiFeature))
286 | {
287 | if (level >= SAM.Levels.HissatsuSenei && IsOffCooldown(SAM.HissatsuSenei))
288 | return SAM.HissatsuSenei;
289 |
290 | if (IsEnabled(CustomComboPreset.SamuraiSeneiGurenFeature))
291 | {
292 | if (level >= SAM.Levels.HissatsuGuren && level < SAM.Levels.HissatsuSenei && IsOffCooldown(SAM.HissatsuGuren))
293 | return SAM.HissatsuGuren;
294 | }
295 | }
296 | }
297 |
298 | return actionID;
299 | }
300 | }
301 |
302 | internal class SamuraiSenei : CustomCombo
303 | {
304 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamAny;
305 |
306 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
307 | {
308 | if (actionID == SAM.HissatsuSenei)
309 | {
310 | if (IsEnabled(CustomComboPreset.SamuraiSeneiGurenFeature))
311 | {
312 | if (level >= SAM.Levels.HissatsuGuren && level < SAM.Levels.HissatsuSenei)
313 | return SAM.HissatsuGuren;
314 | }
315 | }
316 |
317 | return actionID;
318 | }
319 | }
320 |
321 | internal class SamuraiKyuten : CustomCombo
322 | {
323 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamAny;
324 |
325 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
326 | {
327 | if (actionID == SAM.HissatsuKyuten)
328 | {
329 | var gauge = GetJobGauge();
330 |
331 | if (IsEnabled(CustomComboPreset.SamuraiKyutenShoha2Feature))
332 | {
333 | if (level >= SAM.Levels.Shoha2 && gauge.MeditationStacks >= 3)
334 | return SAM.Shoha2;
335 | }
336 |
337 | if (IsEnabled(CustomComboPreset.SamuraiKyutenGurenFeature))
338 | {
339 | if (level >= SAM.Levels.HissatsuGuren && IsOffCooldown(SAM.HissatsuGuren))
340 | return SAM.HissatsuGuren;
341 | }
342 | }
343 |
344 | return actionID;
345 | }
346 | }
347 |
348 | internal class SamuraiIkishoten : CustomCombo
349 | {
350 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.SamuraiIkishotenNamikiriFeature;
351 |
352 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
353 | {
354 | if (actionID == SAM.Ikishoten)
355 | {
356 | if (level >= SAM.Levels.OgiNamikiri)
357 | {
358 | var gauge = GetJobGauge();
359 |
360 | if (IsEnabled(CustomComboPreset.SamuraiIkishotenShohaFeature))
361 | {
362 | if (level >= SAM.Levels.Shoha && gauge.MeditationStacks >= 3)
363 | return SAM.Shoha;
364 | }
365 |
366 | if (gauge.Kaeshi == Kaeshi.NAMIKIRI)
367 | return SAM.KaeshiNamikiri;
368 |
369 | if (HasEffect(SAM.Buffs.OgiNamikiriReady))
370 | return SAM.OgiNamikiri;
371 | }
372 | }
373 |
374 | return actionID;
375 | }
376 | }
377 |
--------------------------------------------------------------------------------
/XIVComboExpanded/XIVComboExpandedPlugin.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | using Dalamud.Game;
5 | using Dalamud.Game.Command;
6 | using Dalamud.Interface.Windowing;
7 | using Dalamud.Plugin;
8 | using Dalamud.Plugin.Services;
9 | using XIVComboExpandedPlugin.Interface;
10 |
11 | namespace XIVComboExpandedPlugin;
12 |
13 | ///
14 | /// Main plugin implementation.
15 | ///
16 | public sealed partial class XIVComboExpandedPlugin : IDalamudPlugin
17 | {
18 | private const string Command = "/pcombo";
19 |
20 | private readonly WindowSystem windowSystem;
21 | private readonly ConfigWindow configWindow;
22 |
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// Dalamud plugin interface.
27 | /// Dalamud signature scanner.
28 | /// Dalamud game interop provider.
29 | public XIVComboExpandedPlugin(
30 | DalamudPluginInterface pluginInterface,
31 | ISigScanner sigScanner,
32 | IGameInteropProvider gameInteropProvider)
33 | {
34 | pluginInterface.Create();
35 |
36 | Service.Configuration = pluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
37 | Service.Address = new PluginAddressResolver();
38 | Service.Address.Setup((SigScanner)sigScanner);
39 |
40 | if (Service.Configuration.Version == 4)
41 | this.UpgradeConfig4();
42 |
43 | Service.ComboCache = new CustomComboCache();
44 | Service.IconReplacer = new IconReplacer(gameInteropProvider);
45 |
46 | this.configWindow = new();
47 | this.windowSystem = new("XIVComboExpanded");
48 | this.windowSystem.AddWindow(this.configWindow);
49 |
50 | Service.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
51 | Service.Interface.UiBuilder.Draw += this.windowSystem.Draw;
52 |
53 | Service.CommandManager.AddHandler(Command, new CommandInfo(this.OnCommand)
54 | {
55 | HelpMessage = "Open a window to edit custom combo settings.",
56 | ShowInHelp = true,
57 | });
58 | }
59 |
60 | ///
61 | public string Name => "XIV Combo Expanded";
62 |
63 | ///
64 | public void Dispose()
65 | {
66 | Service.CommandManager.RemoveHandler(Command);
67 |
68 | Service.Interface.UiBuilder.OpenConfigUi -= this.OnOpenConfigUi;
69 | Service.Interface.UiBuilder.Draw -= this.windowSystem.Draw;
70 |
71 | Service.IconReplacer?.Dispose();
72 | Service.ComboCache?.Dispose();
73 | }
74 |
75 | private void OnOpenConfigUi()
76 | => this.configWindow.IsOpen = true;
77 |
78 | private void OnCommand(string command, string arguments)
79 | {
80 | var argumentsParts = arguments.Split();
81 |
82 | switch (argumentsParts[0])
83 | {
84 | case "setall":
85 | {
86 | foreach (var preset in Enum.GetValues())
87 | {
88 | Service.Configuration.EnabledActions.Add(preset);
89 | }
90 |
91 | Service.ChatGui.Print("All SET");
92 | Service.Configuration.Save();
93 | break;
94 | }
95 |
96 | case "unsetall":
97 | {
98 | foreach (var preset in Enum.GetValues())
99 | {
100 | Service.Configuration.EnabledActions.Remove(preset);
101 | }
102 |
103 | Service.ChatGui.Print("All UNSET");
104 | Service.Configuration.Save();
105 | break;
106 | }
107 |
108 | case "set":
109 | {
110 | var targetPreset = argumentsParts[1].ToLowerInvariant();
111 | foreach (var preset in Enum.GetValues())
112 | {
113 | if (preset.ToString().ToLowerInvariant() != targetPreset)
114 | continue;
115 |
116 | Service.Configuration.EnabledActions.Add(preset);
117 | Service.ChatGui.Print($"{preset} SET");
118 | }
119 |
120 | Service.Configuration.Save();
121 | break;
122 | }
123 |
124 | case "secrets":
125 | {
126 | Service.Configuration.EnableSecretCombos = !Service.Configuration.EnableSecretCombos;
127 |
128 | Service.ChatGui.Print(Service.Configuration.EnableSecretCombos
129 | ? $"Secret combos are now shown"
130 | : $"Secret combos are now hidden");
131 |
132 | Service.Configuration.Save();
133 | break;
134 | }
135 |
136 | case "toggle":
137 | {
138 | var targetPreset = argumentsParts[1].ToLowerInvariant();
139 | foreach (var preset in Enum.GetValues())
140 | {
141 | if (preset.ToString().ToLowerInvariant() != targetPreset)
142 | continue;
143 |
144 | if (Service.Configuration.EnabledActions.Contains(preset))
145 | {
146 | Service.Configuration.EnabledActions.Remove(preset);
147 | Service.ChatGui.Print($"{preset} UNSET");
148 | }
149 | else
150 | {
151 | Service.Configuration.EnabledActions.Add(preset);
152 | Service.ChatGui.Print($"{preset} SET");
153 | }
154 | }
155 |
156 | Service.Configuration.Save();
157 | break;
158 | }
159 |
160 | case "unset":
161 | {
162 | var targetPreset = argumentsParts[1].ToLowerInvariant();
163 | foreach (var preset in Enum.GetValues())
164 | {
165 | if (preset.ToString().ToLowerInvariant() != targetPreset)
166 | continue;
167 |
168 | Service.Configuration.EnabledActions.Remove(preset);
169 | Service.ChatGui.Print($"{preset} UNSET");
170 | }
171 |
172 | Service.Configuration.Save();
173 | break;
174 | }
175 |
176 | case "list":
177 | {
178 | var filter = argumentsParts.Length > 1
179 | ? argumentsParts[1].ToLowerInvariant()
180 | : "all";
181 |
182 | if (filter == "set")
183 | {
184 | foreach (var preset in Enum.GetValues()
185 | .Select(preset => Service.Configuration.IsEnabled(preset)))
186 | {
187 | Service.ChatGui.Print(preset.ToString());
188 | }
189 | }
190 | else if (filter == "unset")
191 | {
192 | foreach (var preset in Enum.GetValues()
193 | .Select(preset => !Service.Configuration.IsEnabled(preset)))
194 | {
195 | Service.ChatGui.Print(preset.ToString());
196 | }
197 | }
198 | else if (filter == "all")
199 | {
200 | foreach (var preset in Enum.GetValues())
201 | {
202 | Service.ChatGui.Print(preset.ToString());
203 | }
204 | }
205 | else
206 | {
207 | Service.ChatGui.PrintError("Available list filters: set, unset, all");
208 | }
209 |
210 | break;
211 | }
212 |
213 | default:
214 | this.configWindow.Toggle();
215 | break;
216 | }
217 |
218 | Service.Configuration.Save();
219 | }
220 |
221 | private void UpgradeConfig4()
222 | {
223 | Service.Configuration.Version = 5;
224 | Service.Configuration.EnabledActions = Service.Configuration.EnabledActions4
225 | .Select(preset => (int)preset switch
226 | {
227 | 27 => 3301,
228 | 75 => 3302,
229 | 73 => 3303,
230 | 25 => 2501,
231 | 26 => 2502,
232 | 56 => 2503,
233 | 70 => 2504,
234 | 71 => 2505,
235 | 110 => 2506,
236 | 95 => 2507,
237 | 41 => 2301,
238 | 42 => 2302,
239 | 63 => 2303,
240 | 74 => 2304,
241 | 33 => 3801,
242 | 31 => 3802,
243 | 34 => 3803,
244 | 43 => 3804,
245 | 50 => 3805,
246 | 72 => 3806,
247 | 103 => 3807,
248 | 44 => 2201,
249 | 0 => 2202,
250 | 1 => 2203,
251 | 2 => 2204,
252 | 3 => 3201,
253 | 4 => 3202,
254 | 57 => 3203,
255 | 85 => 3204,
256 | 20 => 3701,
257 | 52 => 3702,
258 | 96 => 3703,
259 | 97 => 3704,
260 | 22 => 3705,
261 | 30 => 3706,
262 | 83 => 3707,
263 | 84 => 3708,
264 | 23 => 3101,
265 | 24 => 3102,
266 | 47 => 3103,
267 | 58 => 3104,
268 | 66 => 3105,
269 | 102 => 3106,
270 | 54 => 2001,
271 | 82 => 2002,
272 | 106 => 2003,
273 | 17 => 3001,
274 | 18 => 3002,
275 | 19 => 3003,
276 | 87 => 3004,
277 | 88 => 3005,
278 | 89 => 3006,
279 | 90 => 3007,
280 | 91 => 3008,
281 | 92 => 3009,
282 | 107 => 3010,
283 | 108 => 3011,
284 | 5 => 1901,
285 | 6 => 1902,
286 | 59 => 1903,
287 | 7 => 1904,
288 | 55 => 1905,
289 | 86 => 1906,
290 | 69 => 1907,
291 | 48 => 3501,
292 | 49 => 3502,
293 | 68 => 3503,
294 | 53 => 3504,
295 | 93 => 3505,
296 | 101 => 3506,
297 | 94 => 3507,
298 | 11 => 3401,
299 | 12 => 3402,
300 | 13 => 3403,
301 | 14 => 3404,
302 | 15 => 3405,
303 | 81 => 3406,
304 | 60 => 3407,
305 | 61 => 3408,
306 | 64 => 3409,
307 | 65 => 3410,
308 | 109 => 3411,
309 | 29 => 2801,
310 | 37 => 2802,
311 | 39 => 2701,
312 | 40 => 2702,
313 | 8 => 2101,
314 | 9 => 2102,
315 | 10 => 2103,
316 | 78 => 2104,
317 | 79 => 2105,
318 | 67 => 2106,
319 | 104 => 2107,
320 | 35 => 2401,
321 | 36 => 2402,
322 | 76 => 2403,
323 | 77 => 2404,
324 | _ => 0,
325 | })
326 | .Where(id => id != 0)
327 | .Select(id => (CustomComboPreset)id)
328 | .ToHashSet();
329 | Service.Configuration.EnabledActions4 = new();
330 | Service.Configuration.Save();
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/XIVComboExpanded/Combos/MNK.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | using Dalamud.Game.ClientState.JobGauge.Enums;
5 | using Dalamud.Game.ClientState.JobGauge.Types;
6 |
7 | namespace XIVComboExpandedPlugin.Combos;
8 |
9 | internal static class MNK
10 | {
11 | public const byte ClassID = 2;
12 | public const byte JobID = 20;
13 |
14 | public const uint
15 | Bootshine = 53,
16 | TrueStrike = 54,
17 | SnapPunch = 56,
18 | TwinSnakes = 61,
19 | ArmOfTheDestroyer = 62,
20 | Demolish = 66,
21 | PerfectBalance = 69,
22 | Rockbreaker = 70,
23 | DragonKick = 74,
24 | Meditation = 3546,
25 | RiddleOfFire = 7395,
26 | Brotherhood = 7396,
27 | FourPointFury = 16473,
28 | Enlightenment = 16474,
29 | HowlingFist = 25763,
30 | MasterfulBlitz = 25764,
31 | RiddleOfWind = 25766,
32 | ShadowOfTheDestroyer = 25767;
33 |
34 | public static class Buffs
35 | {
36 | public const ushort
37 | OpoOpoForm = 107,
38 | RaptorForm = 108,
39 | CoerlForm = 109,
40 | PerfectBalance = 110,
41 | LeadenFist = 1861,
42 | FormlessFist = 2513,
43 | DisciplinedFist = 3001;
44 | }
45 |
46 | public static class Debuffs
47 | {
48 | public const ushort
49 | Demolish = 246;
50 | }
51 |
52 | public static class Levels
53 | {
54 | public const byte
55 | Bootshine = 1,
56 | TrueStrike = 4,
57 | SnapPunch = 6,
58 | Meditation = 15,
59 | TwinSnakes = 18,
60 | ArmOfTheDestroyer = 26,
61 | Rockbreaker = 30,
62 | Demolish = 30,
63 | FourPointFury = 45,
64 | HowlingFist = 40,
65 | DragonKick = 50,
66 | PerfectBalance = 50,
67 | FormShift = 52,
68 | EnhancedPerfectBalance = 60,
69 | MasterfulBlitz = 60,
70 | RiddleOfFire = 68,
71 | Brotherhood = 70,
72 | Enlightenment = 70,
73 | RiddleOfWind = 72,
74 | ShadowOfTheDestroyer = 82;
75 | }
76 | }
77 |
78 | internal class MonkAoECombo : CustomCombo
79 | {
80 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MonkAoECombo;
81 |
82 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
83 | {
84 | if (actionID == MNK.MasterfulBlitz)
85 | {
86 | var gauge = GetJobGauge();
87 |
88 | // Blitz
89 | if (level >= MNK.Levels.MasterfulBlitz && !gauge.BeastChakra.Contains(BeastChakra.NONE))
90 | return OriginalHook(MNK.MasterfulBlitz);
91 |
92 | if (level >= MNK.Levels.PerfectBalance && HasEffect(MNK.Buffs.PerfectBalance))
93 | {
94 | // Solar
95 | if (level >= MNK.Levels.EnhancedPerfectBalance && !gauge.Nadi.HasFlag(Nadi.SOLAR))
96 | {
97 | if (level >= MNK.Levels.FourPointFury && !gauge.BeastChakra.Contains(BeastChakra.RAPTOR))
98 | return MNK.FourPointFury;
99 |
100 | if (level >= MNK.Levels.Rockbreaker && !gauge.BeastChakra.Contains(BeastChakra.COEURL))
101 | return MNK.Rockbreaker;
102 |
103 | if (level >= MNK.Levels.ArmOfTheDestroyer && !gauge.BeastChakra.Contains(BeastChakra.OPOOPO))
104 | // Shadow of the Destroyer
105 | return OriginalHook(MNK.ArmOfTheDestroyer);
106 |
107 | return level >= MNK.Levels.ShadowOfTheDestroyer
108 | ? MNK.ShadowOfTheDestroyer
109 | : MNK.Rockbreaker;
110 | }
111 |
112 | // Lunar. Also used if we have both Nadi as Tornado Kick/Phantom Rush isn't picky, or under 60.
113 | return level >= MNK.Levels.ShadowOfTheDestroyer
114 | ? MNK.ShadowOfTheDestroyer
115 | : MNK.Rockbreaker;
116 | }
117 |
118 | // FPF with FormShift
119 | if (level >= MNK.Levels.FormShift && HasEffect(MNK.Buffs.FormlessFist))
120 | {
121 | if (level >= MNK.Levels.FourPointFury)
122 | return MNK.FourPointFury;
123 | }
124 |
125 | // 1-2-3 combo
126 | if (level >= MNK.Levels.FourPointFury && HasEffect(MNK.Buffs.RaptorForm))
127 | return MNK.FourPointFury;
128 |
129 | if (level >= MNK.Levels.ArmOfTheDestroyer && HasEffect(MNK.Buffs.OpoOpoForm))
130 | // Shadow of the Destroyer
131 | return OriginalHook(MNK.ArmOfTheDestroyer);
132 |
133 | if (level >= MNK.Levels.Rockbreaker && HasEffect(MNK.Buffs.CoerlForm))
134 | return MNK.Rockbreaker;
135 |
136 | // Shadow of the Destroyer
137 | return OriginalHook(MNK.ArmOfTheDestroyer);
138 | }
139 |
140 | return actionID;
141 | }
142 | }
143 |
144 | internal class MonkHowlingFistEnlightenment : CustomCombo
145 | {
146 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MonkHowlingFistMeditationFeature;
147 |
148 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
149 | {
150 | if (actionID == MNK.HowlingFist || actionID == MNK.Enlightenment)
151 | {
152 | var gauge = GetJobGauge();
153 |
154 | if (level >= MNK.Levels.Meditation && gauge.Chakra < 5)
155 | return MNK.Meditation;
156 | }
157 |
158 | return actionID;
159 | }
160 | }
161 |
162 | internal class MonkDragonKick : CustomCombo
163 | {
164 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MnkAny;
165 |
166 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
167 | {
168 | if (actionID == MNK.DragonKick)
169 | {
170 | var gauge = GetJobGauge();
171 |
172 | if (IsEnabled(CustomComboPreset.MonkDragonKickMeditationFeature))
173 | {
174 | if (level >= MNK.Levels.Meditation && gauge.Chakra < 5 && !InCombat())
175 | return MNK.Meditation;
176 | }
177 |
178 | if (IsEnabled(CustomComboPreset.MonkDragonKickBalanceFeature))
179 | {
180 | if (level >= MNK.Levels.MasterfulBlitz && !gauge.BeastChakra.Contains(BeastChakra.NONE))
181 | return OriginalHook(MNK.MasterfulBlitz);
182 | }
183 |
184 | if (IsEnabled(CustomComboPreset.MonkBootshineFeature))
185 | {
186 | if (level < MNK.Levels.DragonKick)
187 | return MNK.Bootshine;
188 |
189 | if (HasEffect(MNK.Buffs.LeadenFist) && (HasEffect(MNK.Buffs.OpoOpoForm) || HasEffect(MNK.Buffs.PerfectBalance) || HasEffect(MNK.Buffs.FormlessFist)))
190 | return MNK.Bootshine;
191 | }
192 | }
193 |
194 | return actionID;
195 | }
196 | }
197 |
198 | internal class MonkTwinSnakes : CustomCombo
199 | {
200 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MnkAny;
201 |
202 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
203 | {
204 | if (actionID == MNK.TwinSnakes)
205 | {
206 | if (IsEnabled(CustomComboPreset.MonkTwinSnakesFeature))
207 | {
208 | if (level < MNK.Levels.TwinSnakes)
209 | return MNK.TrueStrike;
210 |
211 | if (IsEnabled(CustomComboPreset.MonkFormlessSnakesOption))
212 | {
213 | if (level >= MNK.Levels.FormShift && HasEffect(MNK.Buffs.FormlessFist))
214 | return MNK.TwinSnakes;
215 | }
216 |
217 | if (FindEffect(MNK.Buffs.DisciplinedFist)?.RemainingTime > 6.0)
218 | return MNK.TrueStrike;
219 | }
220 | }
221 |
222 | return actionID;
223 | }
224 | }
225 |
226 | internal class MonkTrueStrike : CustomCombo
227 | {
228 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MonkTrueStrikeFeature;
229 |
230 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
231 | {
232 | if (actionID == MNK.TrueStrike && level >= MNK.Levels.TwinSnakes)
233 | {
234 |
235 | if (IsEnabled(CustomComboPreset.MonkFormlessStrikeOption))
236 | {
237 | if (level >= MNK.Levels.FormShift && HasEffect(MNK.Buffs.FormlessFist))
238 | return MNK.TrueStrike;
239 | }
240 |
241 | var buff = FindEffect(MNK.Buffs.DisciplinedFist);
242 | if (buff == null || buff.RemainingTime <= 6.0)
243 | return MNK.TwinSnakes;
244 | }
245 |
246 | return actionID;
247 | }
248 | }
249 |
250 | internal class MonkDemolish : CustomCombo
251 | {
252 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MnkAny;
253 |
254 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
255 | {
256 | if (actionID == MNK.Demolish)
257 | {
258 | if (IsEnabled(CustomComboPreset.MonkDemolishFeature))
259 | {
260 | if (level < MNK.Levels.Demolish || FindTargetEffect(MNK.Debuffs.Demolish)?.RemainingTime > 6.0)
261 | return MNK.SnapPunch;
262 | }
263 | }
264 |
265 | return actionID;
266 | }
267 | }
268 |
269 | internal class MonkSnapPunch : CustomCombo
270 | {
271 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MnkAny;
272 |
273 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
274 | {
275 | if (actionID == MNK.SnapPunch)
276 | {
277 | if (IsEnabled(CustomComboPreset.MonkSnapPunchFeature))
278 | {
279 | if (level < MNK.Levels.SnapPunch || FindTargetEffect(MNK.Debuffs.Demolish) == null || FindTargetEffect(MNK.Debuffs.Demolish)?.RemainingTime < 6.0)
280 | return MNK.Demolish;
281 | }
282 | }
283 |
284 | return actionID;
285 | }
286 | }
287 |
288 | internal class MonkPerfectBalance : CustomCombo
289 | {
290 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MonkPerfectBalanceFeature;
291 |
292 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
293 | {
294 | if (actionID == MNK.PerfectBalance)
295 | {
296 | var gauge = GetJobGauge();
297 |
298 | if (!gauge.BeastChakra.Contains(BeastChakra.NONE) && level >= MNK.Levels.MasterfulBlitz)
299 | // Chakra actions
300 | return OriginalHook(MNK.MasterfulBlitz);
301 | }
302 |
303 | return actionID;
304 | }
305 | }
306 |
307 | internal class MonkRiddleOfFire : CustomCombo
308 | {
309 | protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.MnkAny;
310 |
311 | protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
312 | {
313 | if (actionID == MNK.RiddleOfFire)
314 | {
315 | var brotherhood = IsEnabled(CustomComboPreset.MonkRiddleOfFireBrotherhood);
316 | var wind = IsEnabled(CustomComboPreset.MonkRiddleOfFireWind);
317 |
318 | if (brotherhood && wind)
319 | {
320 | if (level >= MNK.Levels.RiddleOfWind)
321 | return CalcBestAction(actionID, MNK.RiddleOfFire, MNK.Brotherhood, MNK.RiddleOfWind);
322 |
323 | if (level >= MNK.Levels.Brotherhood)
324 | return CalcBestAction(actionID, MNK.RiddleOfFire, MNK.Brotherhood);
325 |
326 | return actionID;
327 | }
328 |
329 | if (brotherhood)
330 | {
331 | if (level >= MNK.Levels.Brotherhood)
332 | return CalcBestAction(actionID, MNK.RiddleOfFire, MNK.Brotherhood);
333 |
334 | return actionID;
335 | }
336 |
337 | if (wind)
338 | {
339 | if (level >= MNK.Levels.RiddleOfWind)
340 | return CalcBestAction(actionID, MNK.RiddleOfFire, MNK.RiddleOfWind);
341 |
342 | return actionID;
343 | }
344 | }
345 |
346 | return actionID;
347 | }
348 | }
349 |
--------------------------------------------------------------------------------