pf2e
pf2e copied to clipboard
Automate advanced multiclass feats
Automate advanced multiclass feats (e.g. Advanced Arcana, Advanced Maneuver etc) to allow a choice of feats from that class up to half your level (or rather, half the level of the feat slot being used). This has necessitated several changes:
Supporting Changes
New Feat Roll Options
To enable this, I've introduced two new roll option for feats: feat:location:category
and feat:location:level
. These take their values from the feat's location
field. So, for example, a feat with location class-6
would have the roll options:
-
feat:location:category:class
-
feat:location:level:6
Feat Roll Options in Choice Set Predicate
When resolving the predicates for compendium queries in ChoiceSet
rule elements, the item's roll options are also included in the predicate tests. These come under the prefix base-item
to distinguish them from item
(the item being tested from the compendium).
Predication Mathematical Operations
I've added the ability to use mathematical operations within the BinaryOperation
predicates. The available operations are add
(addition), sub
(subtraction), mult
(multiplication), and div
(division). They work similarly to the existing BinaryOperation
elements, taking the form of an object which describes the operation to take place, and a two-length array where the first element is the "left" value and the second element is the "right" value.
For example, the following predicate will return true if the value of item:level
is less than or equal to half the value of self:level
:
{
"lte": [
"item:level",
{
"div": [
"self:level",
2
]
}
]
}
The implementation is fairly simple at the moment (no nested maths operations, only the right operator can be a maths operation) but the way I've written it should allow it to be easily extendable.
Bonus Fix
In the binary operation code, if the right
value was a number, then the left
value would be compared against this same number once for every roll option in the domains
array. I've moved this check to outside the flatMap
operation, so the comparison will only happen once.
Feat Updates
Using the features introduced above, each of the "advanced" multiclass feats has had rule elements applied to query feats belonging to the respective class from the compendium, and then filter so the feat's level cannot be higher than half the level of the feat slot used.
For example, taking Advanced Dogma
in the 6th-level class feat slot will allow a choice of all the 1st- and 2nd-level cleric class feats, whereas taking it in the 8th-level class feat slot will allow a choice of all the 1st-4th-level cleric class feats.
For the case of unlevelled feat slots (e.g. bonus feats) the character's level is used to determine the level of feat that can be taken.
I think we can do a bit better than "self:half-level". Some mechanics do require half level rounded up.
I think we can do a bit better than "self:half-level". Some mechanics do require half level rounded up.
Would half-level-down
be acceptable? Then half-level-up
would be the counterpart and it would be clear what they do.
"self:level:half-down" and "self:level:half-up"
"self:level:half-down" and "self:level:half-up"
Done
Maybe it would be more sensible to set the half level roll option with a RollOption RE on the feat itself rather than doing it for all actors?
This isn't quite right: eligible feats are relative to what level the feat is taken at, not the level of the PC.
Replying to both comments above, so the ideal would be for a roll option set to half the level the feat is taken at, and then the choice set would be based on that roll option.
I see a few potential issues with the implementation:
- The new roll option would have to use the feat's location to set the new roll option. Is this possible?
- Since the same feat can be taken multiple times, the roll option would ideally set itself on the feat rather than the actor. Is this possible?
- Since the roll option would only be used for the parameters on the choice set, maybe it doesn't matter if it gets overridden by later elements, and it can be set on the actor.
- The choice set would then need to refer to the new roll option set by the previous rule element, referring to the on Is this possible?
I'll have to look at all of this when I'm next back at my PC.
@TikaelSol and @stwlam here are the problems I've encountered:
- The
RollOption
rule element adds the option to the actor, not the item itself. Since the same feat could be taken multiple times, we could have several things adding the same roll option. This seems bad. - When attempting to add a
RollOption
rule element before theChoiceSet
rule element, the roll option was not present during resolution of theChoiceSet
rule element. I think this is due to theChoiceSet
choice taking place during pre-create, rather than during normal resolution. - The "location" on the feat is currently a string, e.g.
class-6
. I don't think resolving properties has the ability to extract the "6" from there if it's present, and adding it will not be trivial.
Here's something I've tried which gets me nearly there:
- Instead of having a roll option on the actor for half their level, adding roll options on feats with their location details, i.e. a feat location of
class-6
would result in two roll options (on the feat itself) offeat:location:category:class
andfeat:location:level:6
. * Alter the choice-set rule element to include the base item's (e.g. Advanced Dogma's) roll options as well as the actor's roll options and potential item's roll options.
This allows a predicate like:
{
"lte": [
"item:level",
"base-item:location:level"
]
}
Which means "the item's (i.e. the cleric feat) level must be lower than or equal to the base item's (e.g. Advanced Dogma) location level". That's close, but we still need to halve it. It'd be a bit nasty to include the "half-level" stuff in every feat's roll options.
So, I'm not sure exactly where that leaves us. The current method of using half the actor's level will work for the usual use case of choosing a feat at the character's current level (i.e. upon level-up). The other option is to add some sort of maths processing to predicates, so we could do something like:
{
"lte": [
"item:level",
{
"div": [
"base-item:location:level",
2
]
}
]
}
Which would divide the value of base-item:location:level
by 2 before evaluating against item:level
.
The other option is to add some sort of maths processing to predicates
And this is exactly what I've now done. I've updated the main PR description to explain further.
I've just updated this PR to fix conflicts with more recent changes. Please can someone have another look over this.
@stwlam @TikaelSol do you think you could have another look at this? I've just fixed conflicts from the latest system changes.
Closing this as I have a new pull request after fixing conflicts and simplifying.