|
|
Thread Tools | Display Modes |
Senior Member
Join Date: Oct 2011
Posts: 6,793
|
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. |
#1 |
Senior Member
Join Date: Oct 2011
Posts: 6,793
|
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 ">=" 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 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 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 "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) Code:
~ Spellcasting attribute perform focus.setlinkage[castattr,BaseAttr,"IsAttr.aINT"] Code:
~ Spellcasting type perform focus.assign[CasterType.SpontKnow] perform focus.assign[CasterSrc.Arcane] perform focus.assign[Helper.3rdCaster] perform focus.assign[sClass.cHelpWiz] Last edited by Aaron; February 7th, 2016 at 11:20 AM. |
#2 |
Senior Member
Join Date: Oct 2011
Posts: 6,793
|
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 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) ~ 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" 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" 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" 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." 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." 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." 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." 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." 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." "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" Last edited by Aaron; February 7th, 2016 at 11:16 AM. |
#3 |
Senior Member
Join Date: Oct 2011
Posts: 6,793
|
Reserved For Draft 3
|
#4 |
Senior Member
Join Date: Oct 2011
Posts: 6,793
|
Reserved For Draft 4
|
#5 |
Senior Member
Join Date: Oct 2011
Posts: 6,793
|
Reserved For Draft 5
|
#6 |
Senior Member
Join Date: Jan 2016
Location: Adelaide, Australia
Posts: 2,294
|
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. |
#7 |
Senior Member
Join Date: May 2007
Location: Durham, NC
Posts: 1,747
|
A tag list would also be cool.
|
#8 |
Senior Member
Join Date: Oct 2011
Posts: 6,793
|
|
#9 |
Junior Member
Join Date: Feb 2016
Location: Alaska
Posts: 4
|
|
#10 |
|
|