pf2e icon indicating copy to clipboard operation
pf2e copied to clipboard

Automate advanced multiclass feats

Open JDCalvert opened this issue 2 years ago • 9 comments

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.

JDCalvert avatar Oct 14 '22 22:10 JDCalvert

I think we can do a bit better than "self:half-level". Some mechanics do require half level rounded up.

stwlam avatar Oct 15 '22 02:10 stwlam

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.

JDCalvert avatar Oct 15 '22 07:10 JDCalvert

"self:level:half-down" and "self:level:half-up"

stwlam avatar Oct 15 '22 09:10 stwlam

"self:level:half-down" and "self:level:half-up"

Done

JDCalvert avatar Oct 15 '22 11:10 JDCalvert

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?

TikaelSol avatar Oct 16 '22 00:10 TikaelSol

This isn't quite right: eligible feats are relative to what level the feat is taken at, not the level of the PC.

stwlam avatar Oct 16 '22 06:10 stwlam

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.

JDCalvert avatar Oct 16 '22 07:10 JDCalvert

@TikaelSol and @stwlam here are the problems I've encountered:

  1. 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.
  2. When attempting to add a RollOption rule element before the ChoiceSet rule element, the roll option was not present during resolution of the ChoiceSet rule element. I think this is due to the ChoiceSet choice taking place during pre-create, rather than during normal resolution.
  3. 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) of feat:location:category:class and feat: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.

JDCalvert avatar Oct 17 '22 11:10 JDCalvert

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.

JDCalvert avatar Oct 17 '22 14:10 JDCalvert

I've just updated this PR to fix conflicts with more recent changes. Please can someone have another look over this.

JDCalvert avatar Jan 12 '23 16:01 JDCalvert

@stwlam @TikaelSol do you think you could have another look at this? I've just fixed conflicts from the latest system changes.

JDCalvert avatar Feb 16 '23 10:02 JDCalvert

Closing this as I have a new pull request after fixing conflicts and simplifying.

JDCalvert avatar Jul 24 '23 08:07 JDCalvert