Replacing Archetype Controls
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:
- If an Archetype needs a Lock that someone else already has it should not be allowed
- 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:
- We've reduced each Archetype to having only two tokens to work on overlap (the FACTSET and the APPLY limitation)
- We have used a separate object type LOCK in order to have the FACTSET be typo-safe.
- 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:
- 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.
- 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:
- 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.
- 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.
- Currently this() and target() are in a pending PR that has received comments and is not yet merged.
- 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.
- 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
- If so, we will need GRANTVAR to support Abilities, or some other alternative
- This may require COMPOUND objects to be merged into the master branch (a local branch, not submitted for a PR yet)
- If not, we need to understand what features are required, since it will likely be a DYNAMIC
- 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.
- This may also require additional features to be implemented on DYNAMIC objects
- If so, we will need GRANTVAR to support Abilities, or some other alternative