Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
What this is
I was messing around with different changes/additions to the existing job action replacement logic and felt like there was probably a better way to organize it all. Then I saw the changes you made in the RPR class and it seemed to me like you were starting to experiment with organizing the data better, so I thought I'd submit what I came up with. It's a very heavy rewrite and I wouldn't blame you for pushing back heavily or refusing to implement it, but I had fun working on it either way.
Goals of this refactor
Make it easier to find and tweak both values (skill/buff IDs) and logic.
You already had the skill & buff IDs separated into data container classes, so all I had to do was then also move the skill logic per job into its associated job class.
Encapsulate the ubiquitous patterns in the code/structure to allow better abstraction and optimization (DRY.)
I identified and attempted to encapsulate & abstract the following patterns:
You should be able to tell easily at a glance what a given custom combo preset flag does without comments/a description.
I created some sub-classes whose sole purpose is to organize the replacement logic by custom combo preset flags. This makes writing the logic much more abstracted and also much more readable (in my opinion.) I created convenience functions to make writing the most common types of replacement logic (as identified in the previous point) easier.
The
GetIconDetour
function sometimes has to go through all of the top level conditionals if it doesn't short-circuit early when realistically the plugin should follow a tree structure and always short circuit regardless of if a replacement occurs.Since all replacement logic is now encapsulated within the pre-existing job classes and both the job classes and job action instances are mapped to their own dictionaries, no massive amount of conditional checking is necessary; a tree gets constructed on load, linking all jobs, flags, and actions together.
What this does specifically
Additions
Two new classes were added:
Job is the new base class for all pre-existing data container classes for the jobs. It contains helper functions and logic to facilitate writing action replacement more easily.
JobAction is a data container class which mostly just encapsulates the action ID and level requirement. You can, however, also tack on a "condition" which is what allows you to simply list actions together in a combo without an explicit conditional in code there.
Things that changed
Action replacement logic
All action replacement logic moved from within the
GetIconDetour
to its respective job class. All logic is now contained within one of three functions:How the new action ID gets passed around
iconHook.Original
that it used to.Many things got rescoped
HasMana
function since checking for a minimum amount of black & white mana is used a few times.Action usability is split from how actions are grouped
A brief rundown of how the program flows with this refactor
Initialization
IconReplacer
constructor is called and initializes everything as before.Job.Initialize()
function is called, passing in theclientState
,Configuration
, andiconHook
variables.Job
children with theJobAttribute
as well as the passed in job abbreviations.Job
child is instantiated and mapped in a dictionary to its abbreviation, which is then returned back toIconReplacer
and stored for later use.Main logic
GetIconDetour
is called andlastMove
,comboTime
, andlevel
are derived as before with the addition ofclassAbbreviation
.jobsMap
dictionary returned byJob.Initialize()
is checked against the current class' abbreviation and the corresponding class'ReplaceIcon
function is called which will return true if the current action is mapped (and its flag is enabled.)JobConfig
Job
is instantiated, its constructor will contain calls toForFlag
which maps an internal dictionary entry for that custom combo preset flag with aJobConfigData
instance.JobConfigData
instance can then have a few functions called on it to set up action replacement logic. These functions are:ForAction
,ForComboActions
, andUpgradeAction
Job
ForFlag
,ForAction
,ForComboActions
, andUpgradeAction
are then called to map Flags => Actions => Logic.ForAction
usually utilizesGetBestAvailableAction
which simply returns the right-most action which is usable based on the action conditions set.ForComboActions
andUpgradeAction
are just convenience wrappers ofForAction
andGetBestAvailableAction
.ForComboActions
automatically maps the logic to the last action listed (because the precedent for combo actions is the last action in the combo gets replaced.)UpgradeAction
automatically maps the logic to the first action listed (so that it reads like the first action will be "upgraded" with any of the actions that follow.)Example
Given: A Job has a combo using actions A, B, and C and the level requirement for B is 10 and C is 20.
LastMoveWasInCombo(A)
and the condition of C toLastMoveWasInCombo(B)
. This translates as you would expect to the originalcomboTime > 0 && lastMove == B/C
.ForFlag
call, you would then callForComboActions(A, B, C)
.ForAction
is then called internally and returnsGetBestAvailableAction([A, B, C])
while mapping to all three abilities separately in the internalJobConfigData.actionLogicMap
dictionary.GetBestAvailableAction
then callsCanUse
on its passed array of [A, B, C] to filter it down to only the currently-useable actions. This means if you are only level 1, B and C will be filtered out due to not meeting level requirements. Similarly, if the last move you used was A, then C will be filtered out.GetBestAvailableAction
. This has the additional benefit of easily being able to pass in upgraded action variants right after the normal variant and they'll automatically be used instead as long as you set up their condition logic correctly (see Red Mage's melee combo.)Notes
ForAction
yourself and cannot rely on the convenience functions. E.g. both of Bard's combos map to two abilities in the combo instead of just one like a standard tank 1,2,3 combo.ForAction
calls. Because this all works on tree (dictionary) traversal and not iteration, you can only map an action once.Possible improvements
CheckIsIconReplaceableDetour
could actually check to see if the actionID it's passed is mapped (and its flag is enabled.) I'm assuming this would affect how many skills are passed toGetIconDetour
, so combining this with the first improvement might make a measurable difference on lower-end machines with certain setups.LastMoveWasInCombo
. I didn't because I had to draw the line somewhere on how "convenient" to make everything before it just got ridiculous.