Lone Wolf Development Forums

Lone Wolf Development Forums (http://forums.wolflair.com/index.php)
-   HL - D&D 5th Edition SRD (http://forums.wolflair.com/forumdisplay.php?f=89)
-   -   Tutorial 6 (WiP) - Adding spellcasting to a Class (Eval Scripts & coding) (http://forums.wolflair.com/showthread.php?t=54938)

Aaron February 4th, 2016 01:08 PM

Tutorial 6 (WiP) - Adding spellcasting to a Class (Eval Scripts & coding)
 
Hey guys, I'm happy that people are so enthusiastic about adding their own content, but it's become clear that there is demand for more detail about the coding language HL uses. As such, I am working on a new tutorial which should go out with our next release. I wanted to post the work in progress here in this thread, to give people something to work off of in the meantime and also to invite feedback from the community in case something needs to be explored in more detail.

Keep your eyes here for more updates.

Aaron February 4th, 2016 01:09 PM

Welcome to the weeds, gentlemen! In previous tutorials up until now you've been creating items and assigning them tags and field values, and Hero Lab has been smart enough to interpret those signals and carry out whatever other processes were necessary to complete the work. Here is where you dip your foot into actual coding, by making an eval script that will add spellcasting to a class which does not have it.

What are Eval Scripts?
Eval scripts are how one Pick affects change to itself or another Pick on the hero. Most of the actual coding in hero lab is carried out by Eval scripts, either specific to an individual Pick, or running in the background for all Picks of that type (these background scripts are called "component scripts"). Eval scripts only run for Picks, never for Things, so you'll never see the effect of them until you add the item containing the script to your hero.

What can Eval Scripts do?
Eval scripts can change tags and fields on themselves or another Pick in response to changing conditions of the hero. They can define variables (numbers and text), perform simple math and rounding, and manipulate text in limited ways.

What can't Eval Scripts do?
For the most part, they cannot affect Things (the exception being the name and description fields). They cannot add picks to the hero (although they can alter conditions such that a bootstrap condition is satisfied, and a specific bootstrapped pick is then added to the hero).

Important Eval Script Concept: Timing
Every time something is changed for a hero, every Pick on that character runs all Component Scripts and Eval scripts, and Timing controls what order each of those scripts happen in. It is very important that you choose the right timing for your eval scripts. Too early and information you need may not have been set yet. Too late and any changes you make may happen after the relevant field or tag has been "read" by the next step in whatever process it is involved in.

Timing is divided into several Phases in each game system, which happen in order. Within each phase, priority controls the relative order of eval scripts, with a higher priority occuring later in the process. For 5th edition, the Phases (in order) are:

Testing Global Tags - Ignore this phase, it is too early for anything you may need to do with an eval script.

First - Think carefully about setting an eval script here. Very little has been set about the character at this point, so the most common reason to be doing things here is to satisfy a bootstrap condition (see the section on tag expressions for details on bootstrap conditions)

Pre-Levels - If you need to intervene in some of the processes which will happen in Levels, put your eval script here.

Levels - Ignore this phase for the most part. Most of what you will need to do will rely on what is set in the Levels phase or want to manipulate the processes before they execute. For example, the Proficiency bonus is calculated by component scripts during this phase.

Post-Levels - Anything which relies on the character's level being known should go in this phase. This is probably a safe "default" phase for most things that don't rely on attribute modifiers

Pre-Attributes - Rarely used, most things which can go in this phase will work just as well in Post-Levels.

Attributes - As with Levels, you can ignore this phase and for the same reasons. The attribute modifiers are calculated during this phase.

Post-Attributes - Anything which relies on a character's attribute values or modifiers goes in this phase.

Final - This is the phase when things from various places get pulled together and combined for a final value. For example, the final bonus to damage rolls for a weapon relies on

Render - This phase is for defining the presentation of picks (chiefly designing the name and summary they display when viewed in various parts of Hero Lab) and the application of situational modifiers.

Important Eval Script Concept: Context
Context is where the script is when it executes some action. For eval scripts, the default context for eval scripts is the pick itself. That means we don't need to go anywhere to alter the Pick the eval script is running on, but for any other pick we first have to TRANSITION there. Transitions are represented in the code as dots.

For example, in an eval script if we wanted to access the value of the abilActive field it would be done like so:

field[abilActive].value

Only one transition was necessary.

To access the proficiency bonus, we must first establish a context of the hero, and then transition to the ProfBonus pick, then to the tProfBonus field, and finally to the value of that field, like so:

hero.childfound[ProfBonus].field[tProfBonus].value

Important Eval Script Concept: Conditional scripting (if/then statements)
Conditional statements control sections of an eval script so that the correct code runs for the circumstances. Their general format is:

Code:

if (Statement A  Relationship  Statement B) then
  Do Stuff
  endif

Common relationships are
">=" is "left side greater than or equal to right side"
">" is "left side greater than right side"
"<=" is "left side less than or equal to right side"
"<" is "left side less than right side"
"=" is "both sides are equal"
"<>" is "both sides are not equal"

When the information within the parenthesis is true, then the code inside the statement is carried out.

Conditional statements can have multiple branches (those after the first are preceded by "elseif"), and once one of the branches is found to be true, all subsequent branches are ignored. This allows you to set up heirarchies based on the order you place the statements. For example:

Code:

if (field[xAllLev].value >= 10) then
  Do Stuff A
elseif (field[xAllLev].value >= 5) then
  Do Stuff B
  endif

">=" means the left side is greater than or equal to the right side, so if the xAllLev field value is 10 or higher Code A will run, if it is between 5 and 9 Code B will run, and if it meets neither condition then no code is run at all!

Finally, as the last branch in a conditional statement, you can add a default set of code to run if all other branches fail. This default branch is preceded by "else", like so:

Code:

if (field[xAllLev].value >= 10) then
  Do Stuff A
elseif (field[xAllLev].value >= 5) then
  Do Stuff B
else
  Do Stuff C
  endif

Example: Arcane Dabbler Fighter Martial Archetype
Say we'd like to add a martial archetype of the fighter class which gives them a little bit of spellcasting. Perhaps they dabble in the magical arts in between sword drills and push ups, so we'll call them Arcane Dabblers.

For this tutorial I'll approach things slightly differently. We'll start with some code, then we'll talk about what it is doing and why. You should be well familiar with creating Custom abilities and associating them with an existing class. If you need a refresher, refer back to Tutorial 3.

Once you've created the custom ability, the class abilities that go along with it, and bootstrapped the latter to the former, select the Arcane Dabbler custom special and hit the "Eval Scripts" button in the upper right. Add an Eval script, and for the Phase & Priority choose Pre-Level and 10000. At least some of the component scripts needed by spellcasting rely on Levels and so are set up in the Levels phase. For that reason, we should have our script alter things before that can happen.

Pre-Level 10000
Code:

        perform linkage[table].setfocus

        doneif (state.isfocus = 0)

        doneif (tagis[Helper.Disable] <> 0)

        ~ Spellcasting attribute
        perform focus.setlinkage[castattr,BaseAttr,"IsAttr.aINT"]

        ~ Spellcasting type
        perform focus.assign[CasterType.SpontKnow]
        perform focus.assign[CasterSrc.Arcane]
        perform focus.assign[Helper.3rdCaster]
        perform focus.assign[sClass.cHelpWiz]

        ~ Cantrip Array
        focus.field[cArrKnCan].arrayvalue[2] += 2
        focus.field[cArrKnCan].arrayvalue[9] += 3

        ~ Spells Known Array
        focus.field[cArrKnSpl].arrayvalue[2] += 3
        focus.field[cArrKnSpl].arrayvalue[3] += 4
        focus.field[cArrKnSpl].arrayvalue[6] += 5
        focus.field[cArrKnSpl].arrayvalue[7] += 6
        focus.field[cArrKnSpl].arrayvalue[9] += 7
        focus.field[cArrKnSpl].arrayvalue[10] += 8
        focus.field[cArrKnSpl].arrayvalue[12] += 9
        focus.field[cArrKnSpl].arrayvalue[13] += 10
        focus.field[cArrKnSpl].arrayvalue[15] += 11
        focus.field[cArrKnSpl].arrayvalue[18] += 12
        focus.field[cArrKnSpl].arrayvalue[19] += 13

        ~ Max Spell Levels Array
        focus.field[cArrKnLev].arrayvalue[2] += 1
        focus.field[cArrKnLev].arrayvalue[6] += 2
        focus.field[cArrKnLev].arrayvalue[12] += 3
        focus.field[cArrKnLev].arrayvalue[18] += 4



Code:

        perform linkage[table].setfocus
These first few lines are all about setting a reference context which we can use later in the script. This is largely a conveinence so that we do not need to transition to the class helper over and over again, and also makes our code generalized in case this custom special is added to a class other than the fighter.

"perform" precedes lines of code which manipulate tags and carry out many operations, but is not used when manipulating fields or variables. "linkage[table]" is transitioning the context through a pre-set link to the class helper containing the table this special is added too. Linkages are defined when creating a Thing, they cannot be set up in eval scripts, although an eval script CAN change the target of an existing linkage to a new place. Table linkages are probably the most common one you will use, but there are several others (such as the "save" linkage between the Picks which calculate a characters Save bonus and the attribute those saves rely on). "setfocus" is the operation the line carries out. Think of it like a bookmark for a certain context. Once a focus is set, we can start at that context by using "focus".

Code:

        doneif (state.isfocus = 0)

        doneif (tagis[Helper.Disable] <> 0)

This next two lines are called "Stops", and provides a way to prevent the script from continueing if it should not. Doneifs are a special kind of "if/then" statement which halts the script when the conditions inside the parenthesis is true. The first one checks whether we have successfully set a focus, because if we have not then later code which tries to use a focus will cause an error. The second checks for a tag applied by anything which wants to disable a Custom Special.

Code:

        ~ Spellcasting attribute
        perform focus.setlinkage[castattr,BaseAttr,"IsAttr.aINT"]

By default the fighter class was defined with a castattr linkage to specify which attribute to base spell DCs and attacks on. This line uses focus to start at the class helper, and then makes sure to set the linkage to Intelligence. The first thing listed in the brackets is the linkage to be set at that context, the 2nd and 3rd are the component of the target, and a tag expression to define what in that component should be targetted.

Code:

        ~ Spellcasting type
        perform focus.assign[CasterType.SpontKnow]
        perform focus.assign[CasterSrc.Arcane]
        perform focus.assign[Helper.3rdCaster]
        perform focus.assign[sClass.cHelpWiz]

These four lines assign a set of tags which will control some behaviors. CasterType.SpontKnow marks this is a class which, like the sorcerer, must choose a limited number of known spells, rather than keeping a spellbook. CasterSrc.Arcane defines whether we cast arcane spells or divine spells (which could be important for some things which require one or the other spell types). All classes save the warlock advance along the same progression for spell slots, and Helper.3rdCaster means we add 1/3 our levels towards that. sClass tags are used to set the candidate expression for this class which determines what spells we can learn. In this case we want to use an existing spell list, that of the wizard.

Aaron February 4th, 2016 01:09 PM

What can be done with Tags?
Here are some common things which can be done with tags.

~ add a tag
perform assign[TagGroup.TagID]

~ delete a tag
perform delete[TagGroup.TagID]

~ replace tag #1 with tag #2
perform tagreplace[TagGroup.TagID1,TagGroup.TagID2]

~ pull a tag from the current context to the initial context for the script. In other words, pull the tag from PickID to the Pick which is running this eval script.
perform hero.childfound[PickID].pulltags[TagGroup.TagID]

~ push a tag from the initial context to the current context. In other words, push it from the Pick running this eval script to the PickID Pick.
perform hero.childfound[PickID].pushtags[TagGroup.TagID]

~ similar to pushtags, but the destination is always the container (for most picks, the container is the hero)
perform forward[TagGroup.TagID]

What can be Learned about Tags?
Here are some common things which can be learned about tags in a given context. Note that these operations do not require you to place "perform" before them, and will in fact error if you do so.

~ Is this tag present?
tagis[TagGroup.TagID]

~ How many of this tag are present?
tagcount[TagGroup.TagID]

~ What are the TagIDs of all tags from a tag group, output as a string of text? Note that this uses wildcards (see below)
tagids[TagGroup.?]

~ What are the TagIDs of all tags from a tag group, output as a string of text, including a seperator between each tag? Note that this uses wildcards (see below)
tagids[TagGroup.?,"seperator"]

~ What are the names of all tags from a tag group, output as a string of text? Note that this uses wildcards (see below)
tagnames[TagGroup.?]

~ What are the names of all tags from a tag group, output as a string of text, including a seperator between each tag? Note that this uses wildcards (see below)
tagnames[TagGroup.?,"seperator"]


Wildcards
When dealing with tags, a wildcard allows you to alter several tags that match the TagID up to a certain point marked by a ?. For example, say that your Pick has 3 tags on it "Helper.SetCastNon", "Helper.SetCastBoo", and "Helper.BookRead". Here are some examples of how wildcards would react to that set of tags.

~ This wildcard is at the start, so it catches every tag of the Helper group, deleting all 3 Helper tags.
perform delete[Helper.?]

~ Helper.SetCastNon and Helper.SetCastBoo both match what is defined before the Wildcard, so they are deleted. Helper.BookRead does not match, so it is not deleted.
perform delete[Helper.Set?]

~ Only Helper.SetCastNon matches what is defined before the Wildcard, so it is deleted and the other two remain
perform delete[Helper.SetCastN?]

Wildcards can only be used with tag IDs, not tag groups, and they work for most tag functions except "assign". There is no equivalent when working with fields.

How Do I Know what tags to Manipulate?
The best way to learn what tags do is to look at something already doing what you want to do with a tag, and see the names of the tags present there. In many cases the name/unique ID of the tag will give you a good idea of its function, so start looking there and experiment by assigning those tags. You may also want to see how the tags on a pick react to changes on the hero. For example, if you want to set up an ability that triggers only when you wear heavy armor, you might look at tags on the hero and see what new tags are added when they equip a set of Heavy Armor.

How do I learn what Fields/Tags are Present on a Pick or on the Hero?
To look at field values and tags present, use the Develop menu in hero lab and check "Enable Data File Debugging". This will open up various options. For picks displayed in HL's UI you can right click and select whether you want to copy the unique ID of that pick, see the fields and their values for it, or view the tags present.

For anything not present in the UI, like Class Helpers or other background Picks, you can go to "Develop -> Floating Info Windows -> Show Selection Tags/Fields"

Code:

        ~ Cantrip Array
        focus.field[cArrKnCan].arrayvalue[2] += 2
        focus.field[cArrKnCan].arrayvalue[9] += 3

        ~ Spells Known Array
        focus.field[cArrKnSpl].arrayvalue[2] += 3
        focus.field[cArrKnSpl].arrayvalue[3] += 4
        focus.field[cArrKnSpl].arrayvalue[6] += 5
        focus.field[cArrKnSpl].arrayvalue[7] += 6
        focus.field[cArrKnSpl].arrayvalue[9] += 7
        focus.field[cArrKnSpl].arrayvalue[10] += 8
        focus.field[cArrKnSpl].arrayvalue[12] += 9
        focus.field[cArrKnSpl].arrayvalue[13] += 10
        focus.field[cArrKnSpl].arrayvalue[15] += 11
        focus.field[cArrKnSpl].arrayvalue[18] += 12
        focus.field[cArrKnSpl].arrayvalue[19] += 13

        ~ Max Spell Levels Array
        focus.field[cArrKnLev].arrayvalue[2] += 1
        focus.field[cArrKnLev].arrayvalue[6] += 2
        focus.field[cArrKnLev].arrayvalue[12] += 3
        focus.field[cArrKnLev].arrayvalue[18] += 4

We discussed array fields a bit in Tutorial 3. These lines are setting the contents of various rows of 3 different array fields (the row being targetted is in the final set of brackets). cArrKnCan is an array field which stores the number of known cantrips at certain levels, cArrKnSpl does the same for known spells. cArrKnLev maintains the levels at which the highest spell level that can be known changes.

How do I navigate to different types of value fields?
~ Normal field
field[FieldID].value

~ Array field (rownum is the row of the array you want to manipulate)
field[FieldID].arrayvalue[rownum]

~ Matrix field (rownum is the row of the matrix, and colnum is the column. Together they define the cell in the matrix you want to manipulate)
field[FieldID].matrixvalue[rownum,colnum]


How can Value fields and numeric variables be manipulated?
"+=" add the number to the right
"-=" subtract the number to the right
"*=" multiply by the number to the right
"/=" divide by the number to the right
"=" overwrite the field value with the number on the right (use this with caution, it is almost always better to add some number to 0, rather than overwriting it)

"field[FieldID].value = maximum(field[FieldID].value, number)" increase the field value to a number, if it is less than that number.

"field[FieldID].value = minimum(field[FieldID].value, number)" decrease the field value to a number, if it is greater than that number.

"field[FieldID].value = round(field[FieldID].value, number of decimals, round behavior)" round the value of the field to some number of digits (defined by the second aspect in the parenthesis, 0 meaning to whole numbers). Round behavior of 0 means "round up if .5 or greater", 1 means "always round up", and -1 means "always round down". The first aspect can include some manipulation of its own. For example:

Code:

~ Set our value equal to half our level field's value, rounded to the nearest whole number, and always rounded down.
field[abValue].value = round(field[xAllLev].value/2, 0, -1)

How do I navigate to different types of text fields?
~ Normal field
field[FieldID].text

~ Array field (rownum is the row of the array you want to manipulate)
field[FieldID].arraytext[rownum]

~ Matrix field (rownum is the row of the matrix, and colnum is the column. Together they define the cell in the matrix you want to manipulate)
field[FieldID].matrixtext[rownum,colnum]

How can Text fields and string variables be manipulated?
"&=" append the text to the right to the end of the text on the left

~ Splice some new text into a field, with a seperator if there is already text present (but none if the field was empty before the splice.
field[FieldID].text = splice(field[FieldID].text, "newtext", "seperator")

Example 1:
Code:

~ abText begins empty
field[abText].text = splice(field[abText].text, "dance", " or ")

~ Result is "dance"

Example 2:
Code:

~ abText begins with the text "sing"
field[abText].text = splice(field[abText].text, "dance", " or ")

~ Because the field already has text, the seperator is inserted, so the result is "sing or dance"

The order can be flipped to splice new text at the start rather than the end. Example 3:
Code:

~ abText begins with the text "sing"
field[abText].text = splice("dance", field[abText].text, " or ")

~ Because the field already has text, the seperator is inserted, so the result is "dance or sing"

~ Replace some text in a field, with replacetext if the searchtext is found. Number of replacements of 0 means replace all instances found, any positive number X means replace the first X instances found.
field[FieldID].text = replace(field[FieldID].text, "searchtext", "replacetext", number of replacements)

Example 1:
Code:

~ abText begins with "The brown dog is jumping on the brown boat."
field[abText].text = replace(field[abText].text, "brown", "red", 1)

~ Result is "The red dog is jumping on the brown boat."

Example 2:
Code:

~ abText begins with "The brown dog is jumping on the brown boat."
field[abText].text = replace(field[abText].text, "brown", "red", 0)

~ Result is "The red dog is jumping on the red boat."

Manipulations of Case
lowercase(field[FieldID].text)
Code:

~ abText begins with "The brown dog Cassius punched Hitler."
field[abText].text = lowercase(field[abText].text)

~ Result is "the brown dog cassius punched hitler."

uppercase(field[FieldID].text)
Code:

~ abText begins with "The brown dog Cassius punched Hitler."
field[abText].text = uppercase(field[abText].text)

~ Result is "THE BROWN DOG CASSIUS PUNCHED HITLER."

sentencase(field[FieldID].text)
Code:

~ abText begins with "The brown dog Cassius punched Hitler."
field[abText].text = sentencase(field[abText].text)

~ Result is "The brown dog cassius punched hitler."

titlecase(field[FieldID].text)
Code:

~ abText begins with "The brown dog Cassius punched Hitler."
field[abText].text = titlecase(field[abText].text)

~ Result is "The Brown Dog Cassius Punched Hitler."

Conversion of numbers to Strings of text
"ordinal(number)" converts the number in the parenthesis to an appropriate 3 letter string. For example, 1 becomes "1st", 3 becomes "3rd", 9 becomes "9th".

"signed(number)" converts the number in the parenthesis by prepending a + or - depending on whether the number is positive or negative. For example, 4 becomes "+4", -8 remains "-8"

Aaron February 4th, 2016 01:09 PM

Reserved For Draft 3

Aaron February 4th, 2016 01:09 PM

Reserved For Draft 4

Aaron February 4th, 2016 01:09 PM

Reserved For Draft 5

daplunk February 4th, 2016 02:02 PM

More information on controlling proficiency would be useful.
Tools, Weapons, Armor, Skills.
Example scripts on how to add / remove / check that it exists would be useful.

Frodie February 4th, 2016 02:29 PM

A tag list would also be cool.

Aaron February 4th, 2016 02:35 PM

Quote:

Originally Posted by Frodie (Post 223441)
A tag list would also be cool.

A full list of all tags in any game system would be too massive for any tutorial, but I will talk about the tags used in this instance and how to find existing ones.

warsprite February 5th, 2016 01:06 AM

Quote:

Originally Posted by Aaron (Post 223442)
A full list of all tags in any game system would be too massive for any tutorial, but I will talk about the tags used in this instance and how to find existing ones.

Thanks! Knowing how to find a tag and its groupID would be a big help. :)


All times are GMT -8. The time now is 06:15 AM.

Powered by vBulletin® - Copyright ©2000 - 2024, vBulletin Solutions, Inc.
wolflair.com copyright ©1998-2016 Lone Wolf Development, Inc. View our Privacy Policy here.