@@ -40,6 +40,25 @@ public override readonly int GetHashCode() {
4040 }
4141 }
4242
43+ public struct ClassJobActionInfo {
44+ public uint ActionId ;
45+ public byte Level ;
46+ public bool IsRoleAction ;
47+ }
48+
49+ public struct ClassJobActionEvolution {
50+ public uint ActionId ;
51+ /// <summary>
52+ /// Action ID of the greatest ancestor.
53+ /// </summary>
54+ public uint BaseActionId ;
55+ }
56+
57+ public class ClassJobActionLevelRange {
58+ public byte MinLevel ;
59+ public byte ReplacedLevel ;
60+ }
61+
4362 public unsafe partial class AtkHelper {
4463 public static bool OutOfCombat => ! Dalamud . Condition [ ConditionFlag . InCombat ] ;
4564 public static bool WeaponSheathed => Dalamud . ClientState . LocalPlayer != null && ! Dalamud . ClientState . LocalPlayer . StatusFlags . HasFlag ( StatusFlags . WeaponOut ) ;
@@ -71,6 +90,14 @@ public static void ZoneChanged(ushort e) {
7190 public static int GetIcon ( ActionIds action ) => GetIcon ( ( uint ) action ) ;
7291 public static int GetIcon ( uint action ) => ( int ) ActionToIcon [ action ] ;
7392
93+ public static bool IsActionAvailableAtLevel ( ActionIds action , byte level ) => IsActionAvailableAtLevel ( ( uint ) action , level ) ;
94+ public static bool IsActionAvailableAtLevel ( uint action , byte level ) {
95+ if ( level == 0 ) return true ;
96+ if ( ClassJobActionIdToInfo . GetValueOrDefault ( action ) . IsRoleAction ) return true ;
97+ if ( ! ClassJobActionToLevelRange . TryGetValue ( action , out var levelRange ) ) return true ;
98+ return level >= levelRange . MinLevel && ( levelRange . ReplacedLevel == 0 || level < levelRange . ReplacedLevel ) ;
99+ }
100+
74101 public static JobIds IdToJob ( uint job ) => job < 19 ? JobIds . OTHER : ( JobIds ) job ;
75102
76103 private static IEnumerable < ClassJob > JobSheet ;
@@ -80,6 +107,8 @@ public static void ZoneChanged(ushort e) {
80107 // Cache converted strings
81108 private static Dictionary < JobIds , string > JobToString ;
82109 private static Dictionary < Item , string > ItemToString ;
110+ private static Dictionary < uint , ClassJobActionInfo > ClassJobActionIdToInfo = new ( ) ;
111+ private static Dictionary < uint , ClassJobActionLevelRange > ClassJobActionToLevelRange = new ( ) ;
83112
84113 public static string Localize ( JobIds job ) {
85114 if ( JobToString . TryGetValue ( job , out var jobString ) ) return jobString ;
@@ -143,10 +172,10 @@ private static void SetupSheets() {
143172 foreach ( var item in ActionSheet ) {
144173 var name = item . Name . ToString ( ) ;
145174 var attackType = item . ActionCategory . Value . Name . ToString ( ) ;
146- var actionId = item . ActionCategory . Value . RowId ;
175+ var categoryId = item . ActionCategory . Value . RowId ;
147176 if ( item . Icon != 405 && item . Icon != 0 ) ActionToIcon [ item . RowId ] = item . Icon ;
148177
149- if ( actionId == 2 || actionId == 3 ) { // spell or weaponskill
178+ if ( categoryId == 2 || categoryId == 3 ) { // spell or weaponskill
150179 if ( item . CooldownGroup == 58 || item . AdditionalCooldownGroup == 58 ) GCDs . Add ( item . RowId ) ; // not actually a gcd
151180 }
152181
@@ -158,6 +187,12 @@ private static void SetupSheets() {
158187 Type = ItemType . Action
159188 }
160189 } ) ;
190+
191+ ClassJobActionIdToInfo . Add ( item . RowId , new ClassJobActionInfo {
192+ ActionId = item . RowId ,
193+ Level = item . ClassJobLevel ,
194+ IsRoleAction = item . IsRoleAction
195+ } ) ;
161196 }
162197
163198 StatusSheet = Dalamud . DataManager . GetExcelSheet < Lumina . Excel . GeneratedSheets . Status > ( ) . Where ( x => ! string . IsNullOrEmpty ( x . Name ) ) ;
@@ -173,6 +208,55 @@ private static void SetupSheets() {
173208 }
174209
175210 JobSheet = Dalamud . DataManager . GetExcelSheet < ClassJob > ( ) . Where ( x => x . Name != null ) ;
211+
212+ SetupClassJobActionLevelRanges ( ClassJobActionIdToInfo ) ;
213+ }
214+
215+ private static void SetupClassJobActionLevelRanges ( Dictionary < uint , ClassJobActionInfo > classJobActionIdToInfo ) {
216+ var actionEvoBases = Dalamud . DataManager . GetExcelSheet < ClassJobActionSort > ( )
217+ . Where ( row =>
218+ row . Unknown1 /*BaseActionId*/ != 0 &&
219+ classJobActionIdToInfo . ContainsKey ( row . Unknown0 /*ActionId*/ ) )
220+ . Select ( row => row . Unknown1 /*BaseActionId*/ )
221+ . ToHashSet ( ) ;
222+
223+ var actionEvos = Dalamud . DataManager . GetExcelSheet < ClassJobActionSort > ( )
224+ . Where ( row => actionEvoBases . Contains ( row . Unknown1 /*BaseActionId*/ ) )
225+ . Select ( row => new ClassJobActionEvolution {
226+ ActionId = row . Unknown0 ,
227+ BaseActionId = row . Unknown1
228+ } )
229+ . ToList ( ) ;
230+
231+ Dictionary < uint , List < uint > > baseActionToActionGroup = new ( ) ;
232+ foreach ( var evo in actionEvos ) {
233+ if ( ! classJobActionIdToInfo . ContainsKey ( evo . ActionId ) ) continue ;
234+ if ( ! baseActionToActionGroup . TryGetValue ( evo . BaseActionId , out var actionGroup ) ) {
235+ actionGroup = new ( ) ;
236+ baseActionToActionGroup . Add ( evo . BaseActionId , actionGroup ) ;
237+ }
238+ actionGroup . Add ( evo . ActionId ) ;
239+ }
240+
241+ foreach ( var actionGroup in baseActionToActionGroup . Values ) {
242+ actionGroup . Sort ( ( a1 , a2 ) => {
243+ var a1Level = classJobActionIdToInfo . GetValueOrDefault ( a1 ) . Level ;
244+ var a2Level = classJobActionIdToInfo . GetValueOrDefault ( a2 ) . Level ;
245+ return a1Level - a2Level ;
246+ } ) ;
247+
248+ for ( int i = 0 ; i < actionGroup . Count ; i ++ ) {
249+ byte minLevel = classJobActionIdToInfo . GetValueOrDefault ( actionGroup [ i ] ) . Level ;
250+ byte replacedLevel = 0 ;
251+ if ( i < actionGroup . Count - 1 ) {
252+ replacedLevel = classJobActionIdToInfo . GetValueOrDefault ( actionGroup [ i + 1 ] ) . Level ;
253+ }
254+ ClassJobActionToLevelRange . Add ( actionGroup [ i ] , new ClassJobActionLevelRange {
255+ MinLevel = minLevel ,
256+ ReplacedLevel = replacedLevel
257+ } ) ;
258+ }
259+ }
176260 }
177261
178262 public static float TimeLeft ( float defaultDuration , Dictionary < Item , Status > buffDict , Item lastActiveTrigger , DateTime lastActiveTime ) {
0 commit comments