Formula Parser Conversion Instructions

From PCGen Wiki
Revision as of 19:56, 26 March 2016 by LegacyKing (talk | contribs) (Correct syntax)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Code Changes

Preparation

  • Define the name of the feature to be converted. Add the name of the feature to pcgen.cdom.enumeration.FeatureKey ... For purposes of the rest of this description, it is called "FKEY"
  • Define the namespace and name of the new variable. For purposes of the rest of this description, we will use numbers (VAR namespace) and "NEWVAR" as the variable name

Tokens

  • Determine if it is a full token or a partial output token that is being deprecated
    • In the case of a full token, Take the existing output token(s) and deprecate the output token. Note: It is NOT necessary in this case to support ANY output if the feature is set to FORMULA.
    • In the case of a partial token, then a transition is required (mainly to be able to increment across the same items consistently), and the Feature being set to FORMULA needs to trigger the old output token to draw from the new system

Bracket the "old style output" as such:

if (SettingsHandler.getGame().getFeature(FeatureKey.FKEY).equals(FeatureState.FORMULA))
{
  //This assumes just simple formatting, if something more complex is needed
  //take the returned object and cast / format as necessary
  return pc.getGlobal("VAR", "NEWVAR").toString();
}
else
{
  //old behavior
}
  • Determine if any BONUS tokens are involved. If so, determine if it is a full or partial BONUS token For purposes of the rest of this description, we assume it is BONUSTYPE BONUSNAME, e.g.:
BONUS:BONUSTYPE|BONUSNAME|...
  • If full:
    • Take the existing BONUS token and deprecate it
    • Add an error message to the token parse when the feature is on (there are various ways to do this depending on the type of BONUS token being altered).
  • If partial, you will need to gate some capability based on the feature setting. For a MultiTagBonusObject this might look like:
@Override
protected void addBonusInfo(Object obj)
{
  FeatureState featurestate = SettingsHandler.getGame().getFeature(FeatureKey.FKEY);
  if (featurestate.equals(FeatureState.FORMULA) && Integer.valueOf(3).equals(obj))
  {
    Logging.errorPrint("BONUS BONUSTYPE|BONUSNAME is not supported when Feature FKEY=FORMULA");
  }
  super.addBonusInfo(obj);
}

Note in this example that the bonusname being removed is in the 4th position (index 3) of the array used in the MultiTagBonusObject.

Enable

  • Define the feature in the loader. This is done in pcgen.persistence.SourceFileLoader ... There are two convenience methods provided, so the only changes need to occur in the defineBuiltinVariables method. Ensure there is an appropriate defineNamespace call for the format and name of the namespace being used, e.g.:
defineNamespace(varContext, Number.class, "VAR");

The insert a line to define the variable:

defineVariable(varContext, "VAR", "NewVar");

This enables the use of MODIFY and MODIFYOTHER to modify that variable.

Data Changes

Token Replacement

Search the data for the appropriate tag and/or bonus that is part of the subsystem being replaced, e.g.:

BONUS:BONUSTYPE|BONUSNAME|...

Note that the ability to "chain" multiple BONUS names needs to be considered when you search!! That means things of the form:

BONUS:WEAPON|ATTACK,DAMAGE|...

...are possible. So you can't literally search for just the type and name next to each other and assume it will work (many of you know that, but just a fair reminder)

Each of the old tokens and BONUSes should be removed and replaced.

Replacement Location

The key is: WHERE should the modify go?

Philosophy

Remember: There IS NO PRExxx on MODIFY. Don't expect one. This is a "positive thinking" process. Take the value you need and apply it where you need it. Preferably directly to something that appears on a character sheet.

The appropriate location is NOT necessarily where the tag or BONUS was. The question that should be asked is "why is this not on the object as directly referenced in the rule book?" That should be the default location, and only changed in rare cases.

This is an important request: If you don't think you can put the MODIFY directly on the object where it appears in the rule book, I would ask you to STOP and ASK. MODIFY and MODIFYOTHER are rather powerful, but perhaps in subtle ways that are not immediately obvious. The data will be better if folks act in a purposeful way rather than trying to rush.

Example

If the tag was directly on the object that in the rules defines the change and there was no PRExxx, then the MOFIFY probably goes directly on the object.

If the tag has a PRExxx, it is likely then it is useful to know where the modification occurred in the rules. If it occurred on a specific ability, but was dependent upon a specific race, then it should go on the ability, and modify the race:

MODIFYOTHER:RACE|Dwarf|NEWVAR|ADD|1

Note for those that haven't read the inner workings in detail, yes, NEWVAR can be a global variable in this case. What happens is that when the Ability is added to the PC, it looks to see if "Dwarf" is applied to the PC. If it is, the ADD is applied. Also, if "Dwarf" is later applied, then the ADD is also applied. So MODIFYOTHER works regardless of the order of addition of the source and target of the MODIFYOTHER.

This would behave much like:

BONUS:VAR|OLDVAR|1|PRERACE:1,Dwarf

(but is a whole LOT faster than the old system)

If the tag is the result of a "dummy object" (e.g. It is applying FACE:20 in a Template called FACE_20, then *get rid of the dummy object*. Assign items where the rules apply them. Put the MODIFY of the Face directly on the item that specified the new FACE in the rules book.

The "dummy object reduction" is significant for 3 reasons:

  • This is not generally how the data is designed today. There are often "dummy abilities", "dummy templates" or other things that exist. These exist for multiple reasons, many of those are from limitations posed by the existing tokens... but the new system has no such restriction, so the "dummy objects" are only a penalty
  • The "dummy objects" take processing time. Every time an object is added to a PC, it takes something on the order of 100 queries on that object to check if it contains information for all of the possible tokens on the object. If the object only does one thing, that is just wasted time
  • "Dummy objects" are hard to diagnose. If you have a new FACE, but it was applied by FACE_20, is that useful? It is comforting (from a consistency perspective) that FACE_20 applied MODIFY:AREA=Face|SET|20,20 (both being 20 and all) ... but it's actually rather useless to assist in understanding why FACE_20 was applied in the first place. In the old system, there was a complex set of output tokens that could hopefully be used to expose something about the internal guts of PCGen. Some of the "dummy objects" are even in existence to help with this diagnosis, because the ability to output exactly what Abilities a PC has is mostly easy to access from a diagnosis output sheet. However, with the new system, there is a "diagnosis system" already built in to the UI. It tells you every action taken by the system (in pretty gory detail) and exactly the object that caused the change to the variable. So the problem with a "dummy object" is that the change was triggered by that object, but you still need ask another question to figure out why the dummy object was applied in the first place. The "indirection" caused by the "dummy object" complicates debugging... so it should be avoided.