Replacing Archetype Controls

From PCGen Wiki
Jump to: navigation, search


Current State

Archetype prerequisites are fairly complex. Currently, there are a tremendous number of FACTS (literally thousands) used to control what Archetype can be taken. More items are then used to determine how to apply each item. Much of this depends on the Default object and trailing PRExxx. Following the logic is ... difficult, and the performance is probably not great, due to lots and lots of trailing PRExxx.

Using the new formula system

Limiting Selection

First of all, we define a new type of object called LOCK:

In a DATACONTROL file:

DYNAMICSCOPE:LOCK

We then define each type of lock:

In a DYNAMIC File:

LOCK:Fighter_3_SomethingOrOther

Now we want a place to store all the locks we have encountered on the PC:

In a VARIABLE file:

GLOBAL:ARRAY[LOCK]=AllLocks

Now we recognize that we want 2 things to happen:

  1. If an Archetype needs a Lock that someone else already has it should not be allowed
  2. An Archetype should grant all of the locks it needs (to "claim" them)

Note: At this point, we need to suspend disbelief for a moment, since it's not quite defined if Archetypes will be Ability objects or if they are better served as something else. For the rest of this explanation, it assumes there is a type called ARCHETYPE to avoid too much complexity in the example.

There are 2 things that need the locks, and in software development, it tends to be useful to type things once and use them in two places, to avoid things becoming inconsistent. In this case, we can use a FACTSET to do that:

We define the FACTSET:

FACTSETDEF:ARCHETYPE|Locks <> DATAFORMAT:LOCK

Then we put the locks in a FACTSET on the Archetype:

MyArchetype <> FACTSET:Locks|Fighter_3_SomethingOrOther

Now we need to use that FACTSET. To avoid allowing the Archetype to be used if any of its Locks are already claimed we use this:

ALLOW:isEmpty(overlap(AllLocks,getFactSet("ARCHETYPE", this(), "Locks")))

This is very much new relative to other formulas so let's explain this in a bit of detail.

This uses the "this()" function. We could have hardcoded the FACTSET to look up the Archetype itself, e.g.:

getFactSet("ARCHETYPE", "MyArchetype", "Locks")

...however that encounters a problem if we .COPY MyArchetype... so we don't want that hardcoding. We use the this() function to allow the reference to automatically update to the object on which the item is applied.

Second, it performs the function overlap:

overlap(X,Y)

This hasn't been seen in previous examples.

This will take two arrays and return a subset of items that are in both arrays.

So AllLocks is ARRAY[LOCK], and the getFactSet call will also return an ARRAY[LOCK]. Any LOCK in both arrays (which indicates a conflict which should prohibit application of the Archetype) will be in the returned array.

We then ensure that array is empty:

isEmpty(...)

If it's empty, taking the Archetype is allowed.

Lastly, we need to have the Archetype grant its locks. We can do this without having a token on each Archetype by using the Global Modifier file:

MODIFYOTHER:ARCHETYPE|ALL|AllLocks|ADD|getFactSet(target(),"Locks")

This uses the "target()" function. We want to avoid placing this MODIFYOTHER on every Archetype - ANNOYING. We want to do that once, so we put it in the Global Modifier file and it will apply to all Archetypes. Note just like the logic on "this()" earlier, we have a function called "target()" (which is where the MODIFYOTHER is applied) which is generic enough that we can write this token ONCE and have it apply to every Archetype.

So here is what we have:

  1. We've reduced each Archetype to having only two tokens to work on overlap (the FACTSET and the APPLY limitation)
  2. We have used a separate object type LOCK in order to have the FACTSET be typo-safe.
    1. Meaning it's not just general String in the FACTSET, so you have to consciously put the FACT in one file and then use it in other LST files

Overriding the Feature

Now each Archetype needs to override a class feature. To do that, we simply want to treat each class feature as a variable, so it can be easily overridden using the MODIFY* system:

In VARIABLE file:

LOCAL:CLASS|ARCHETYPE=Level_3_Feature

In CLASS LST file:

CLASS:Fighter <> MODIFY:Level_3_Feature|SET|Default_L3_Feature_Fighter
CLASS:Fighter <> GRANTVAR:Level_3_Feature

Note that this puts an Archetype into a Variable and then "grants" the contents of that variable to the PC. If we want to change what is granted, we simply need to change the contents of that variable:

In ARCHETYPE LST file:

MyArchetype <> MODIFYOTHER:CLASS|Fighter|Level_3_Feature|SET|SomeCustomFeature|PRIORITY=400

Note a few advantages here as well:

  1. This is all DIRECTLY in the objects either owning (CLASS) or modifying (ARCHETYPE) the situation. No setting variables to go trigger a PRExxx that is somewhere over on a Default object. If you can't write something directly in the new system, SPEAK UP. The goal is for it to be very straightforward.
  2. This is now all in "forward logic" - there are no trailing PRExxx here that can "turn things on or off" (and have a performance impact because they have to constantly be tested). Either the base feature is present, or it's overridden - reconsideration is only necessary at the moment a new Archetype is applied, and then we "know" with certainty whether the base or custom feature is present.

Gaps as of March 8 2018

In short, this is close to being doable, but not doable with the current master branch. It is hoped that this is doable by the time we reach 6.8. What specifically is needed:

  1. Currently isEmpty(X) is implemented but has not yet been merged with the master branch. It is currently flowing through from the Formula library, and should be merged well before the 6.8 release.
  2. Currently GRANTVAR exists in a local branch, but has not yet been submitted for a PR due to complexity and lots of other competing PRs.
  3. Currently this() and target() are in a pending PR that has received comments and is not yet merged.
  4. Currently overlap(X,Y) is not implemented. It is fairly trivial, but will be done in the formula system and take a library update integration to occur. It should occur by the 6.8 release.
  5. We need to have a discussion over what ARCHETYPE is (and whether it must be an Ability). GRANTVAR and Abilities are likely to not get along well (due to Category/Nature/etc). So we need to decide if these HAVE to be Ability objects
    1. If so, we will need GRANTVAR to support Abilities, or some other alternative
      1. This may require COMPOUND objects to be merged into the master branch (a local branch, not submitted for a PR yet)
    2. If not, we need to understand what features are required, since it will likely be a DYNAMIC
      1. This will require Interface Tokens to be merged into the master branch (a local branch, not submitted for a PR yet), so that DYNAMIC objects can contain MODIFY* tokens.
      2. This may also require additional features to be implemented on DYNAMIC objects